parent
24958d4896
commit
2111fcac62
|
|
@ -1671,6 +1671,40 @@ pub struct TableWrite {
|
|||
const MAX_ITERATIONS: usize = 10_000;
|
||||
const MAX_CALL_DEPTH: u32 = 256;
|
||||
|
||||
thread_local! {
|
||||
static RNG_STATE: std::cell::Cell<u64> = std::cell::Cell::new(seed_from_time());
|
||||
}
|
||||
|
||||
fn seed_from_time() -> u64 {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let nanos = SystemTime::now().duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_nanos() as u64).unwrap_or(0x9E3779B97F4A7C15);
|
||||
nanos.wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEFCAFEBABE
|
||||
}
|
||||
|
||||
/// xorshift64* — pulls the next u64 from the thread-local PRNG.
|
||||
fn rng_next_u64() -> u64 {
|
||||
RNG_STATE.with(|s| {
|
||||
let mut x = s.get();
|
||||
if x == 0 { x = 0x9E3779B97F4A7C15; }
|
||||
x ^= x >> 12;
|
||||
x ^= x << 25;
|
||||
x ^= x >> 27;
|
||||
s.set(x);
|
||||
x.wrapping_mul(0x2545F4914F6CDD1D)
|
||||
})
|
||||
}
|
||||
|
||||
/// uniform f64 in [0, 1).
|
||||
fn rng_next_unit() -> f64 {
|
||||
(rng_next_u64() >> 11) as f64 / (1u64 << 53) as f64
|
||||
}
|
||||
|
||||
/// seeds the thread-local PRNG to a deterministic value.
|
||||
pub fn seed_rng(seed: u64) {
|
||||
RNG_STATE.with(|s| s.set(if seed == 0 { 0x9E3779B97F4A7C15 } else { seed }));
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
pub fn new() -> Self {
|
||||
Interpreter {
|
||||
|
|
@ -2403,6 +2437,52 @@ impl Interpreter {
|
|||
};
|
||||
return Ok(retag_spice(Value::Number(result), unit));
|
||||
}
|
||||
"rand" => {
|
||||
match args.len() {
|
||||
0 => return Ok(Value::Number(rng_next_unit())),
|
||||
1 => {
|
||||
let v = self.eval_expr(&args[0], depth)?;
|
||||
match v {
|
||||
Value::Number(n) => {
|
||||
if n <= 0.0 { return Err("rand(n) expects n > 0".into()); }
|
||||
return Ok(Value::Number((rng_next_unit() * n).floor()));
|
||||
}
|
||||
Value::Array(a) => {
|
||||
if a.is_empty() { return Err("rand(array) on empty array".into()); }
|
||||
let idx = (rng_next_unit() * a.len() as f64) as usize;
|
||||
return Ok(a[idx.min(a.len() - 1)].clone());
|
||||
}
|
||||
_ => return Err("rand(x) expects a number or array".into()),
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let lo = match self.eval_expr(&args[0], depth)? {
|
||||
Value::Number(n) => n,
|
||||
_ => return Err("rand(lo, hi) expects numbers".into()),
|
||||
};
|
||||
let hi = match self.eval_expr(&args[1], depth)? {
|
||||
Value::Number(n) => n,
|
||||
_ => return Err("rand(lo, hi) expects numbers".into()),
|
||||
};
|
||||
if hi <= lo { return Err("rand(lo, hi) requires hi > lo".into()); }
|
||||
return Ok(Value::Number(lo + rng_next_unit() * (hi - lo)));
|
||||
}
|
||||
_ => return Err("rand() takes 0, 1, or 2 arguments".into()),
|
||||
}
|
||||
}
|
||||
"seed" => {
|
||||
if args.len() != 1 {
|
||||
return Err("seed() expects 1 argument".into());
|
||||
}
|
||||
let v = self.eval_expr(&args[0], depth)?;
|
||||
match v {
|
||||
Value::Number(n) => {
|
||||
seed_rng(n.to_bits());
|
||||
return Ok(Value::Void);
|
||||
}
|
||||
_ => return Err("seed() expects a number".into()),
|
||||
}
|
||||
}
|
||||
"floor" | "ceil" | "round" => {
|
||||
if args.is_empty() || args.len() > 2 {
|
||||
return Err(format!("{}() expects 1 or 2 arguments", name));
|
||||
|
|
@ -6084,6 +6164,80 @@ fn find(arr, target) {
|
|||
assert!(matches!(v, Value::Number(n) if n == 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rand_unit_range() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(42)").unwrap();
|
||||
for _ in 0..20 {
|
||||
let v = i.eval_expr_str("rand()").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n >= 0.0 && n < 1.0, "rand() = {}", n),
|
||||
_ => panic!("rand() should return Number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rand_integer_bound() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(7)").unwrap();
|
||||
for _ in 0..30 {
|
||||
let v = i.eval_expr_str("rand(10)").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n >= 0.0 && n < 10.0 && n == n.trunc()),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rand_lo_hi_range() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(99)").unwrap();
|
||||
for _ in 0..30 {
|
||||
let v = i.eval_expr_str("rand(5, 8)").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n >= 5.0 && n < 8.0),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rand_picks_from_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(1)").unwrap();
|
||||
let v = i.eval_expr_str("rand([10, 20, 30])").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!(n == 10.0 || n == 20.0 || n == 30.0),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rand_seed_is_deterministic() {
|
||||
let mut a = Interpreter::new();
|
||||
a.exec_line("seed(12345)").unwrap();
|
||||
let va = a.eval_expr_str("rand()").unwrap();
|
||||
a.exec_line("seed(12345)").unwrap();
|
||||
let vb = a.eval_expr_str("rand()").unwrap();
|
||||
match (va, vb) {
|
||||
(Value::Number(x), Value::Number(y)) => assert_eq!(x.to_bits(), y.to_bits()),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rand_method_call_on_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("seed(3)").unwrap();
|
||||
let v = i.eval_expr_str("[1, 2, 3, 4, 5].rand()").unwrap();
|
||||
match v {
|
||||
Value::Number(n) => assert!((1.0..=5.0).contains(&n)),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_loop_does_not_leak_var() {
|
||||
let mut i = Interpreter::new();
|
||||
|
|
|
|||
|
|
@ -728,6 +728,8 @@ fn is_cordial_builtin(w: &str) -> bool {
|
|||
| "ring" | "iter" | "peek" | "history"
|
||||
// aggregates
|
||||
| "sum" | "avg" | "min" | "max" | "count" | "std_devp" | "std_devs"
|
||||
// random
|
||||
| "rand" | "seed"
|
||||
// constants
|
||||
| "pi"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue