Feat: adding logrand and exprand
This commit is contained in:
@@ -53,6 +53,8 @@ pub enum Op {
|
|||||||
Set,
|
Set,
|
||||||
GetContext(String),
|
GetContext(String),
|
||||||
Rand,
|
Rand,
|
||||||
|
ExpRand,
|
||||||
|
LogRand,
|
||||||
Seed,
|
Seed,
|
||||||
Cycle,
|
Cycle,
|
||||||
PCycle,
|
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 => {
|
Op::Seed => {
|
||||||
let s = stack.pop().ok_or("stack underflow")?.as_int()?;
|
let s = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||||
*self.rng.lock().unwrap() = StdRng::seed_from_u64(s as u64);
|
*self.rng.lock().unwrap() = StdRng::seed_from_u64(s as u64);
|
||||||
|
|||||||
@@ -473,6 +473,26 @@ pub const WORDS: &[Word] = &[
|
|||||||
compile: Simple,
|
compile: Simple,
|
||||||
varargs: false,
|
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 {
|
Word {
|
||||||
name: "seed",
|
name: "seed",
|
||||||
aliases: &[],
|
aliases: &[],
|
||||||
@@ -2508,6 +2528,8 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
|
|||||||
"sound" => Op::NewCmd,
|
"sound" => Op::NewCmd,
|
||||||
"." => Op::Emit,
|
"." => Op::Emit,
|
||||||
"rand" => Op::Rand,
|
"rand" => Op::Rand,
|
||||||
|
"exprand" => Op::ExpRand,
|
||||||
|
"logrand" => Op::LogRand,
|
||||||
"seed" => Op::Seed,
|
"seed" => Op::Seed,
|
||||||
"cycle" => Op::Cycle,
|
"cycle" => Op::Cycle,
|
||||||
"pcycle" => Op::PCycle,
|
"pcycle" => Op::PCycle,
|
||||||
|
|||||||
@@ -105,3 +105,69 @@ fn ftom_880() {
|
|||||||
fn mtof_ftom_roundtrip() {
|
fn mtof_ftom_roundtrip() {
|
||||||
expect_float("60 mtof ftom", 60.0);
|
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