mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-10 15:42:01 +03:00
add random() with deterministic line-seeded PRNG
This commit is contained in:
parent
532c3265ad
commit
1d77ee45d8
5 changed files with 45 additions and 4 deletions
|
|
@ -246,6 +246,18 @@ describe("variables and document context", () => {
|
|||
it("product", () => {
|
||||
expect(calcDoc("2\n3\n4\nproduct")).toEqual(["2", "3", "4", "24"]);
|
||||
});
|
||||
it("random — deterministic per line text", () => {
|
||||
const [a] = calcDoc("random(1, 6)");
|
||||
const [b] = calcDoc("random(1, 6)");
|
||||
expect(a).toBe(b); // same text → same seed → same result
|
||||
const [c] = calcDoc("random(1, 6) ");
|
||||
expect(c).not.toBe(a); // different text → different seed
|
||||
});
|
||||
it("random — stays in range", () => {
|
||||
const v = Number(calc("random(1, 100)"));
|
||||
expect(v).toBeGreaterThanOrEqual(1);
|
||||
expect(v).toBeLessThanOrEqual(100);
|
||||
});
|
||||
it("headers split blocks and produce no result", () => {
|
||||
expect(calcDoc("# Food\n10\n20\nsum")).toEqual([null, "10", "20", "30"]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ export interface LineCtx {
|
|||
/** per-line flags for block boundaries */
|
||||
lineKinds: ("empty" | "header" | "normal")[];
|
||||
index: number;
|
||||
/** raw line text — used to seed random() deterministically */
|
||||
lineText: string;
|
||||
}
|
||||
|
||||
export interface EvalCtx {
|
||||
|
|
@ -490,10 +492,35 @@ function evalCall(name: string, args: Value[], ctx: EvalCtx): Value {
|
|||
case "ceil": return x.kind === "quantity" ? { ...x, value: x.value.ceil() } : qty(n().ceil());
|
||||
case "floor": return x.kind === "quantity" ? { ...x, value: x.value.floor() } : qty(n().floor());
|
||||
case "fact": case "factorial": return qty(factorial(n()));
|
||||
case "random": {
|
||||
const seed = strHash(ctx.line.lineText) ^ (ctx.line.index * 0x9e3779b9);
|
||||
const rand = mulberry32(seed);
|
||||
if (args.length === 0) return qty(new Decimal(rand));
|
||||
const toNum = (v: Value) => v.kind === "quantity" ? v.value.toNumber() : 0;
|
||||
const [lo, hi] = args.length === 1 ? [0, toNum(args[0]!)] : [toNum(args[0]!), toNum(args[1]!)];
|
||||
const isInt = Number.isInteger(lo) && Number.isInteger(hi);
|
||||
const raw = isInt ? Math.floor(rand * (hi - lo + 1)) + lo : rand * (hi - lo) + lo;
|
||||
return qty(new Decimal(raw));
|
||||
}
|
||||
default: throw new EvalError(`unknown function ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
function strHash(s: string): number {
|
||||
let h = 0x811c9dc5;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
h = Math.imul(h ^ s.charCodeAt(i), 0x01000193);
|
||||
}
|
||||
return h >>> 0;
|
||||
}
|
||||
|
||||
function mulberry32(seed: number): number {
|
||||
seed = (seed + 0x6D2B79F5) | 0;
|
||||
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
||||
t = t + Math.imul(t ^ (t >>> 7), 61 | t) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
}
|
||||
|
||||
function factorial(n: Decimal): Decimal {
|
||||
if (!n.isInteger() || n.isNegative() || n.gt(300)) throw new EvalError("bad factorial");
|
||||
let acc = new Decimal(1);
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export class SumEngine {
|
|||
const ctx: EvalCtx = {
|
||||
reg: this.reg,
|
||||
vars,
|
||||
line: { lineValues, lineKinds, index: i },
|
||||
line: { lineValues, lineKinds, index: i, lineText: rawLine },
|
||||
};
|
||||
try {
|
||||
value = evaluate(parsed.expr, ctx);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const BUILTIN_FUNCS = [
|
|||
"sqrt", "cbrt", "sin", "cos", "tan", "cot", "asin", "acos", "atan",
|
||||
"arcsin", "arccos", "arctan", "sinh", "cosh", "tanh",
|
||||
"ln", "lg", "log", "log2", "exp", "abs", "round", "ceil", "floor",
|
||||
"fact", "factorial",
|
||||
"fact", "factorial", "random",
|
||||
];
|
||||
|
||||
export class Registry {
|
||||
|
|
|
|||
|
|
@ -431,7 +431,8 @@ export const EN: VocabLocale = {
|
|||
"count.variants": "count,how many",
|
||||
"min.variants": "min,minimum,smallest",
|
||||
"max.variants": "max,maximum,largest",
|
||||
"product.variants": "product"
|
||||
"product.variants": "product",
|
||||
"random.variants": "random,rand"
|
||||
},
|
||||
"Percent": {
|
||||
"as_a_percent_of.variants": "as a % of,as a percent of",
|
||||
|
|
@ -907,7 +908,8 @@ export const RU: VocabLocale = {
|
|||
"count.variants": "количество,кол-во,счёт,число строк",
|
||||
"min.variants": "минимум,мин,наименьшее",
|
||||
"max.variants": "максимум,макс,наибольшее",
|
||||
"product.variants": "произведение,произв"
|
||||
"product.variants": "произведение,произв",
|
||||
"random.variants": "случайное,случайно,рандом"
|
||||
},
|
||||
"Percent": {
|
||||
"as_a_percent_of.variants": "как процент от,как процент",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue