From 15476c2f07796b34da6fb86ae8b0e529221d6d83 Mon Sep 17 00:00:00 2001 From: cs01 Date: Sun, 1 Mar 2026 19:31:19 -0800 Subject: [PATCH] add seeded prng: Math.seedRandom() and lib/random.ts lcg --- lib/random.ts | 22 ++++++++++++ src/codegen/stdlib/math.ts | 15 +++++++++ tests/fixtures/math/math-seed-random.ts | 27 +++++++++++++++ tests/fixtures/random/random-lib-basic.ts | 41 +++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 lib/random.ts create mode 100644 tests/fixtures/math/math-seed-random.ts create mode 100644 tests/fixtures/random/random-lib-basic.ts diff --git a/lib/random.ts b/lib/random.ts new file mode 100644 index 00000000..ff4ab3aa --- /dev/null +++ b/lib/random.ts @@ -0,0 +1,22 @@ +export class Random { + private state: number; + + constructor(seed: number) { + this.state = seed % 4294967296.0; + if (this.state < 0.0) { + this.state = this.state + 4294967296.0; + } + if (this.state === 0.0) { + this.state = 1.0; + } + } + + next(): number { + this.state = (this.state * 1664525.0 + 1013904223.0) % 4294967296.0; + return this.state / 4294967296.0; + } + + nextInt(min: number, max: number): number { + return Math.floor(this.next() * (max - min)) + min; + } +} diff --git a/src/codegen/stdlib/math.ts b/src/codegen/stdlib/math.ts index e672988c..721fb118 100644 --- a/src/codegen/stdlib/math.ts +++ b/src/codegen/stdlib/math.ts @@ -53,6 +53,7 @@ export class MathGenerator { "cos", "sign", "random", + "seedRandom", ]; } @@ -95,6 +96,8 @@ export class MathGenerator { return this.generateSign(expr, params); case "random": return this.generateRandom(expr); + case "seedRandom": + return this.generateSeedRandom(expr, params); default: return this.ctx.emitError(`Unsupported Math method: ${method}`, expr.loc); } @@ -216,6 +219,18 @@ export class MathGenerator { return result; } + private generateSeedRandom(expr: MethodCallNode, params: string[]): string { + if (expr.args.length !== 1) { + return this.ctx.emitError("Math.seedRandom() requires 1 argument", expr.loc); + } + const seed = this.ctx.generateExpression(expr.args[0], params); + const dblSeed = this.ctx.ensureDouble(seed); + const i64Seed = this.ctx.nextTemp(); + this.ctx.emit(`${i64Seed} = fptosi double ${dblSeed} to i64`); + this.ctx.emitCallVoid("@srand48", `i64 ${i64Seed}`); + return "0.0"; + } + private generateSign(expr: MethodCallNode, params: string[]): string { if (expr.args.length !== 1) { return this.ctx.emitError("Math.sign() requires 1 argument", expr.loc); diff --git a/tests/fixtures/math/math-seed-random.ts b/tests/fixtures/math/math-seed-random.ts new file mode 100644 index 00000000..638cf907 --- /dev/null +++ b/tests/fixtures/math/math-seed-random.ts @@ -0,0 +1,27 @@ +Math.seedRandom(42); +const a1 = Math.random(); +const a2 = Math.random(); + +Math.seedRandom(42); +const b1 = Math.random(); +const b2 = Math.random(); + +if (a1 !== b1 || a2 !== b2) { + console.log("FAIL: same seed produced different sequences"); + process.exit(1); +} + +Math.seedRandom(99); +const c1 = Math.random(); + +if (c1 === a1) { + console.log("FAIL: different seeds produced same first value"); + process.exit(1); +} + +if (a1 < 0 || a1 >= 1 || b1 < 0 || b1 >= 1) { + console.log("FAIL: values out of [0, 1) range"); + process.exit(1); +} + +console.log("TEST_PASSED"); diff --git a/tests/fixtures/random/random-lib-basic.ts b/tests/fixtures/random/random-lib-basic.ts new file mode 100644 index 00000000..6dca0c20 --- /dev/null +++ b/tests/fixtures/random/random-lib-basic.ts @@ -0,0 +1,41 @@ +import { Random } from "../../../lib/random.js"; + +const rng1 = new Random(12345); +const a = rng1.next(); +const b = rng1.next(); +const c = rng1.next(); + +if (a < 0 || a >= 1 || b < 0 || b >= 1 || c < 0 || c >= 1) { + console.log("FAIL: values out of [0, 1) range"); + process.exit(1); +} + +if (a === b && b === c) { + console.log("FAIL: all values identical"); + process.exit(1); +} + +const rng2 = new Random(12345); +const d = rng2.next(); +const e = rng2.next(); +const f = rng2.next(); + +if (a !== d || b !== e || c !== f) { + console.log("FAIL: same seed produced different sequences"); + process.exit(1); +} + +const rng3 = new Random(99999); +const g = rng3.next(); +if (g === a) { + console.log("FAIL: different seeds produced same first value"); + process.exit(1); +} + +const n = rng1.nextInt(0, 10); +if (n < 0 || n >= 10) { + console.log("FAIL: nextInt out of range"); + process.exit(1); +} + +console.log("TEST_PASSED");