Feat: adding logrand and exprand
This commit is contained in:
@@ -53,6 +53,8 @@ pub enum Op {
|
||||
Set,
|
||||
GetContext(String),
|
||||
Rand,
|
||||
ExpRand,
|
||||
LogRand,
|
||||
Seed,
|
||||
Cycle,
|
||||
PCycle,
|
||||
|
||||
@@ -460,6 +460,28 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
}
|
||||
Op::ExpRand => {
|
||||
let hi = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let lo = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
if lo <= 0.0 || hi <= 0.0 {
|
||||
return Err("exprand requires positive values".into());
|
||||
}
|
||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||
let u: f64 = self.rng.lock().unwrap().gen();
|
||||
let val = lo * (hi / lo).powf(u);
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::LogRand => {
|
||||
let hi = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let lo = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
if lo <= 0.0 || hi <= 0.0 {
|
||||
return Err("logrand requires positive values".into());
|
||||
}
|
||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||
let u: f64 = self.rng.lock().unwrap().gen();
|
||||
let val = hi * (lo / hi).powf(u);
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::Seed => {
|
||||
let s = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
*self.rng.lock().unwrap() = StdRng::seed_from_u64(s as u64);
|
||||
|
||||
@@ -473,6 +473,26 @@ pub const WORDS: &[Word] = &[
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "exprand",
|
||||
aliases: &[],
|
||||
category: "Probability",
|
||||
stack: "(lo hi -- f)",
|
||||
desc: "Exponential random biased toward lo. Both args must be positive",
|
||||
example: "1.0 100.0 exprand => 3.7",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "logrand",
|
||||
aliases: &[],
|
||||
category: "Probability",
|
||||
stack: "(lo hi -- f)",
|
||||
desc: "Exponential random biased toward hi. Both args must be positive",
|
||||
example: "1.0 100.0 logrand => 87.2",
|
||||
compile: Simple,
|
||||
varargs: false,
|
||||
},
|
||||
Word {
|
||||
name: "seed",
|
||||
aliases: &[],
|
||||
@@ -2508,6 +2528,8 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
||||
"sound" => Op::NewCmd,
|
||||
"." => Op::Emit,
|
||||
"rand" => Op::Rand,
|
||||
"exprand" => Op::ExpRand,
|
||||
"logrand" => Op::LogRand,
|
||||
"seed" => Op::Seed,
|
||||
"cycle" => Op::Cycle,
|
||||
"pcycle" => Op::PCycle,
|
||||
|
||||
@@ -105,3 +105,69 @@ fn ftom_880() {
|
||||
fn mtof_ftom_roundtrip() {
|
||||
expect_float("60 mtof ftom", 60.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exprand_in_range() {
|
||||
let f = forth_seeded(12345);
|
||||
f.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap();
|
||||
let val = stack_float(&f);
|
||||
assert!(val >= 1.0 && val <= 100.0, "exprand {} not in [1, 100]", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exprand_deterministic() {
|
||||
let f1 = forth_seeded(99);
|
||||
let f2 = forth_seeded(99);
|
||||
f1.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap();
|
||||
f2.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap();
|
||||
assert_eq!(f1.stack(), f2.stack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exprand_swapped_args() {
|
||||
let f1 = forth_seeded(42);
|
||||
let f2 = forth_seeded(42);
|
||||
f1.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap();
|
||||
f2.evaluate("100.0 1.0 exprand", &default_ctx()).unwrap();
|
||||
assert_eq!(f1.stack(), f2.stack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exprand_requires_positive() {
|
||||
expect_error("0.0 10.0 exprand", "exprand requires positive values");
|
||||
expect_error("-1.0 10.0 exprand", "exprand requires positive values");
|
||||
expect_error("1.0 0.0 exprand", "exprand requires positive values");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logrand_in_range() {
|
||||
let f = forth_seeded(12345);
|
||||
f.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap();
|
||||
let val = stack_float(&f);
|
||||
assert!(val >= 1.0 && val <= 100.0, "logrand {} not in [1, 100]", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logrand_deterministic() {
|
||||
let f1 = forth_seeded(99);
|
||||
let f2 = forth_seeded(99);
|
||||
f1.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap();
|
||||
f2.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap();
|
||||
assert_eq!(f1.stack(), f2.stack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logrand_swapped_args() {
|
||||
let f1 = forth_seeded(42);
|
||||
let f2 = forth_seeded(42);
|
||||
f1.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap();
|
||||
f2.evaluate("100.0 1.0 logrand", &default_ctx()).unwrap();
|
||||
assert_eq!(f1.stack(), f2.stack());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logrand_requires_positive() {
|
||||
expect_error("0.0 10.0 logrand", "logrand requires positive values");
|
||||
expect_error("-1.0 10.0 logrand", "logrand requires positive values");
|
||||
expect_error("1.0 0.0 logrand", "logrand requires positive values");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user