From bf2611a1be30aa9f0bd9774e194cc0e176f6cbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:09:53 +0900 Subject: [PATCH 1/7] Updated `vite.config.js` so that it matches the `tsconfig.json` --- playground/vite.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playground/vite.config.js b/playground/vite.config.js index 4025d882..5fcc624e 100644 --- a/playground/vite.config.js +++ b/playground/vite.config.js @@ -10,4 +10,7 @@ export default defineConfig({ allow: [ '..' ] } }, + build: { + target: "es2022" + }, }) From 8eb041ce0323df648adefeb1474fb6cf02ab6156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:12:16 +0900 Subject: [PATCH 2/7] Implemented unbiased variant of `Math:rnd` and `Math:gen_rng_unbiased` --- src/interpreter/lib/std.ts | 28 ++++++++++++++++++++++-- src/interpreter/util.ts | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 40c320e3..3801db0b 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -2,7 +2,7 @@ import { v4 as uuid } from 'uuid'; import seedrandom from 'seedrandom'; import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; -import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue } from '../util.js'; +import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue, unbiasedRandomIntegerInRange, cryptoGen64, signedNumber32ToBigUint32 } from '../util.js'; import { AiScriptRuntimeError } from '../../error.js'; import type { Value } from '../value.js'; @@ -412,12 +412,19 @@ export const std: Record = { return NUM(Math.random()); }), + 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { + assertNumber(min); + assertNumber(max); + const array = new BigUint64Array(1); + const result = unbiasedRandomIntegerInRange(min.value, max.value, array, cryptoGen64); + return typeof result === 'number' ? NUM(result) : NULL; + }), + 'Math:gen_rng': FN_NATIVE(([seed]) => { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; const rng = seedrandom(seed.value.toString()); - return FN_NATIVE(([min, max]) => { if (min && min.type === 'num' && max && max.type === 'num') { return NUM(Math.floor(rng() * (Math.floor(max.value) - Math.ceil(min.value) + 1) + Math.ceil(min.value))); @@ -425,6 +432,23 @@ export const std: Record = { return NUM(rng()); }); }), + + 'Math:gen_rng_unbiased': FN_NATIVE(([seed]) => { + expectAny(seed); + if (seed.type !== 'num' && seed.type !== 'str') return NULL; + + const rng = seedrandom(seed.value.toString()); + return FN_NATIVE(([min, max]) => { + assertNumber(min); + assertNumber(max); + const result = unbiasedRandomIntegerInRange(min.value, max.value, rng, (rng) => { + const upper = signedNumber32ToBigUint32(rng.int32()); + const lower = signedNumber32ToBigUint32(rng.int32()); + return BigInt.asUintN(64, (upper << 32n) | lower); + }); + return typeof result === 'number' ? NUM(result) : NULL; + }); + }), //#endregion //#region Num diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts index 1d60c0f3..2ede1660 100644 --- a/src/interpreter/util.ts +++ b/src/interpreter/util.ts @@ -193,3 +193,47 @@ export function reprValue(value: Value, literalLike = false, processedObjects = return '?'; } + +function clz64(num: number): number { + const q = num / (2 ** 32); + const r = num % (2 ** 32); + const upper = Math.clz32(q); + const lower = Math.clz32(r) + 32; + return upper < 32 ? upper : lower; +} + +export function cryptoGen64(array: BigUint64Array): bigint | null { + if (array.length < 1) return null; + const generated = crypto.getRandomValues(array)[0]; + if (!generated) { + return null; + } + return generated; +} + +export function signedNumber32ToBigUint32(num: number) : bigint { + return BigInt((num & 0x7fffffff) - (num & 0x80000000)); // bitwise operators always treats numbers as 32bit signed integers, but arithmetic operators don't. +} + +export function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null { + const ceilMin = Math.ceil(min); + const floorMax = Math.floor(max); + const signedScale = floorMax - ceilMin; + if (signedScale === 0) return 0; + const scale = Math.abs(signedScale); + const scaleSign = Math.sign(signedScale); + if (!Number.isSafeInteger(scale) || !Number.isSafeInteger(ceilMin) || !Number.isSafeInteger(floorMax)) { + return null; + } + const bigScale = BigInt(scale); + const shift = BigInt(clz64(scale)); // scale is already proven to be a safe integer + let result: bigint; + do { + const generated = gen64(generator); + if (!generated) { + return null; + } + result = generated >> shift; + } while (result > bigScale); + return Number(result) * scaleSign + ceilMin; +} From 5ae8288f47e0b973d7fb41b913a73f2b9fee0eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 17:09:31 +0900 Subject: [PATCH 3/7] Updated `aiscript.api.md` --- etc/aiscript.api.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index c430eb08..0593f82f 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -271,6 +271,9 @@ type Continue_2 = NodeBase_2 & { type: 'continue'; }; +// @public (undocumented) +function cryptoGen64(array: BigUint64Array): bigint | null; + declare namespace Cst { export { isStatement_2 as isStatement, @@ -858,6 +861,9 @@ export class Scope { }; } +// @public (undocumented) +function signedNumber32ToBigUint32(num: number): bigint; + // @public (undocumented) type Statement = Definition | Return | Each | For | Loop | Break | Continue | Assign | AddAssign | SubAssign; @@ -918,6 +924,9 @@ type TypeSource = NamedTypeSource | FnTypeSource; // @public (undocumented) type TypeSource_2 = NamedTypeSource_2 | FnTypeSource_2; +// @public (undocumented) +function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null; + // @public (undocumented) const unWrapRet: (v: Value) => Value; @@ -941,7 +950,10 @@ declare namespace utils { valToJs, jsToVal, getLangVersion, - reprValue + reprValue, + cryptoGen64, + signedNumber32ToBigUint32, + unbiasedRandomIntegerInRange } } export { utils } From 94c33c259c8866065f20714e228bf821407b78c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 18:58:41 +0900 Subject: [PATCH 4/7] * Refactored `Math:rnd` to use crypto * Refactored `Math:rnd_unbiased` to use crypto * Refactored `Math:gen_rng_unbiased` to use `SeedRandomWrapper` --- src/interpreter/lib/std.ts | 22 ++++---- src/interpreter/util.ts | 44 ---------------- src/utils/random/CryptoGen.ts | 29 +++++++++++ src/utils/random/randomBase.ts | 92 ++++++++++++++++++++++++++++++++++ src/utils/random/seedrandom.ts | 76 ++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 56 deletions(-) create mode 100644 src/utils/random/CryptoGen.ts create mode 100644 src/utils/random/randomBase.ts create mode 100644 src/utils/random/seedrandom.ts diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 3801db0b..b12e0df8 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -2,8 +2,10 @@ import { v4 as uuid } from 'uuid'; import seedrandom from 'seedrandom'; import { NUM, STR, FN_NATIVE, FALSE, TRUE, ARR, NULL, BOOL, OBJ, ERROR } from '../value.js'; -import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue, unbiasedRandomIntegerInRange, cryptoGen64, signedNumber32ToBigUint32 } from '../util.js'; +import { assertNumber, assertString, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny, assertArray, reprValue } from '../util.js'; import { AiScriptRuntimeError } from '../../error.js'; +import { CryptoGen } from '../../utils/random/CryptoGen.js'; +import { SeedRandomWrapper } from '../../utils/random/seedrandom.js'; import type { Value } from '../value.js'; export const std: Record = { @@ -407,17 +409,17 @@ export const std: Record = { 'Math:rnd': FN_NATIVE(([min, max]) => { if (min && min.type === 'num' && max && max.type === 'num') { - return NUM(Math.floor(Math.random() * (Math.floor(max.value) - Math.ceil(min.value) + 1) + Math.ceil(min.value))); + const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); + return res === null ? NULL : NUM(res); } - return NUM(Math.random()); + return NUM(CryptoGen.instance.generateNumber0To1()); }), 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { assertNumber(min); assertNumber(max); - const array = new BigUint64Array(1); - const result = unbiasedRandomIntegerInRange(min.value, max.value, array, cryptoGen64); - return typeof result === 'number' ? NUM(result) : NULL; + const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); + return res === null ? NULL : NUM(res); }), 'Math:gen_rng': FN_NATIVE(([seed]) => { @@ -437,15 +439,11 @@ export const std: Record = { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; - const rng = seedrandom(seed.value.toString()); + const rng = new SeedRandomWrapper(seed.value); return FN_NATIVE(([min, max]) => { assertNumber(min); assertNumber(max); - const result = unbiasedRandomIntegerInRange(min.value, max.value, rng, (rng) => { - const upper = signedNumber32ToBigUint32(rng.int32()); - const lower = signedNumber32ToBigUint32(rng.int32()); - return BigInt.asUintN(64, (upper << 32n) | lower); - }); + const result = rng.generateRandomIntegerInRange(min.value, max.value); return typeof result === 'number' ? NUM(result) : NULL; }); }), diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts index 2ede1660..1d60c0f3 100644 --- a/src/interpreter/util.ts +++ b/src/interpreter/util.ts @@ -193,47 +193,3 @@ export function reprValue(value: Value, literalLike = false, processedObjects = return '?'; } - -function clz64(num: number): number { - const q = num / (2 ** 32); - const r = num % (2 ** 32); - const upper = Math.clz32(q); - const lower = Math.clz32(r) + 32; - return upper < 32 ? upper : lower; -} - -export function cryptoGen64(array: BigUint64Array): bigint | null { - if (array.length < 1) return null; - const generated = crypto.getRandomValues(array)[0]; - if (!generated) { - return null; - } - return generated; -} - -export function signedNumber32ToBigUint32(num: number) : bigint { - return BigInt((num & 0x7fffffff) - (num & 0x80000000)); // bitwise operators always treats numbers as 32bit signed integers, but arithmetic operators don't. -} - -export function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null { - const ceilMin = Math.ceil(min); - const floorMax = Math.floor(max); - const signedScale = floorMax - ceilMin; - if (signedScale === 0) return 0; - const scale = Math.abs(signedScale); - const scaleSign = Math.sign(signedScale); - if (!Number.isSafeInteger(scale) || !Number.isSafeInteger(ceilMin) || !Number.isSafeInteger(floorMax)) { - return null; - } - const bigScale = BigInt(scale); - const shift = BigInt(clz64(scale)); // scale is already proven to be a safe integer - let result: bigint; - do { - const generated = gen64(generator); - if (!generated) { - return null; - } - result = generated >> shift; - } while (result > bigScale); - return Number(result) * scaleSign + ceilMin; -} diff --git a/src/utils/random/CryptoGen.ts b/src/utils/random/CryptoGen.ts new file mode 100644 index 00000000..c167c82f --- /dev/null +++ b/src/utils/random/CryptoGen.ts @@ -0,0 +1,29 @@ +import { RandomBase, readBigUintLittleEndian } from './randomBase.js'; + +export class CryptoGen extends RandomBase { + private static _instance: CryptoGen = new CryptoGen(); + public static get instance() : CryptoGen { + return CryptoGen._instance; + } + + private constructor() { + super(); + } + + protected generateBigUintByBytes(bytes: number): bigint { + let u8a = new Uint8Array(Math.ceil(bytes / 8) * 8); + if (u8a.length < 1 || !Number.isSafeInteger(bytes)) return 0n; + u8a = this.generateBytes(u8a.subarray(0, bytes)); + return readBigUintLittleEndian(u8a.buffer) ?? 0n; + } + + public generateBigUintByBits(bits: number): bigint { + if (bits < 1 || !Number.isSafeInteger(bits)) return 0n; + const bytes = Math.ceil(bits / 8); + const wastedBits = BigInt(bytes * 8 - bits); + return this.generateBigUintByBytes(bytes) >> wastedBits; + } + public generateBytes(array: Uint8Array): Uint8Array { + return crypto.getRandomValues(array); + } +} diff --git a/src/utils/random/randomBase.ts b/src/utils/random/randomBase.ts new file mode 100644 index 00000000..3123fd07 --- /dev/null +++ b/src/utils/random/randomBase.ts @@ -0,0 +1,92 @@ +export const safeIntegerBits = Math.ceil(Math.log2(Number.MAX_SAFE_INTEGER)); +export const bigSafeIntegerBits = BigInt(safeIntegerBits); +export const bigMaxSafeIntegerExclusive = 1n << bigSafeIntegerBits; +export const fractionBits = safeIntegerBits - 1; +export const bigFractionBits = BigInt(fractionBits); + +export abstract class RandomBase { + protected abstract generateBigUintByBytes(bytes: number): bigint; + public abstract generateBigUintByBits(bits: number): bigint; + public abstract generateBytes(array: Uint8Array): Uint8Array; + + public generateNumber0To1(): number { + let res = this.generateBigUintByBits(safeIntegerBits); + let exponent = 1022; + let remainingFractionBits = safeIntegerBits - bitsToRepresent(res); + while (remainingFractionBits > 0 && exponent >= safeIntegerBits) { + exponent -= remainingFractionBits; + res <<= BigInt(remainingFractionBits); + res |= this.generateBigUintByBits(remainingFractionBits); + remainingFractionBits = safeIntegerBits - bitsToRepresent(res); + } + if (remainingFractionBits > 0) { + const shift = Math.min(exponent - 1, remainingFractionBits); + res <<= BigInt(shift); + res |= this.generateBigUintByBits(shift); + exponent = Math.max(exponent - shift, 0); + } + return (Number(res) * 0.5 ** safeIntegerBits) * (0.5 ** (1022 - exponent)); + } + + public generateUniform(maxInclusive: bigint): bigint { + if (maxInclusive < 1) return 0n; + const log2 = maxInclusive.toString(2).length; + const bytes = Math.ceil(log2 / 8); + const wastedBits = BigInt(bytes * 8 - log2); + let result: bigint; + do { + result = this.generateBigUintByBytes(bytes) >> wastedBits; + } while (result > maxInclusive); + return result; + } + + public generateRandomIntegerInRange(min: number, max: number): number | null { + const ceilMin = Math.ceil(min); + const floorMax = Math.floor(max); + const signedScale = floorMax - ceilMin; + if (signedScale === 0) return ceilMin; + const scale = Math.abs(signedScale); + const scaleSign = Math.sign(signedScale); + if (!Number.isSafeInteger(scale) || !Number.isSafeInteger(ceilMin) || !Number.isSafeInteger(floorMax)) { + return null; + } + const bigScale = BigInt(scale); + return Number(this.generateUniform(bigScale)) * scaleSign + ceilMin; + } +} + +export function bitsToRepresent(num: bigint): number { + if (num === 0n) return 0; + return num.toString(2).length; +} + +function readSmallBigUintLittleEndian(buffer: ArrayBufferLike): bigint | null { + if (buffer.byteLength === 0) return null; + if (buffer.byteLength < 8) { + const array = new Uint8Array(8); + array.set(new Uint8Array(buffer)); + return new DataView(array.buffer).getBigUint64(0, true); + } + return new DataView(buffer).getBigUint64(0, true); +} + +export function readBigUintLittleEndian(buffer: ArrayBufferLike): bigint | null { + if (buffer.byteLength === 0) return null; + if (buffer.byteLength <= 8) { + return readSmallBigUintLittleEndian(buffer); + } + const dataView = new DataView(buffer); + let pos = 0n; + let res = 0n; + let index = 0; + for (; index < dataView.byteLength - 7; index += 8, pos += 64n) { + const element = dataView.getBigUint64(index, true); + res |= element << pos; + } + if (index < dataView.byteLength) { + const array = new Uint8Array(8); + array.set(new Uint8Array(buffer, index)); + res |= new DataView(array.buffer).getBigUint64(0, true) << pos; + } + return res; +} diff --git a/src/utils/random/seedrandom.ts b/src/utils/random/seedrandom.ts new file mode 100644 index 00000000..5c6b3d5f --- /dev/null +++ b/src/utils/random/seedrandom.ts @@ -0,0 +1,76 @@ +import seedrandom from 'seedrandom'; +import { RandomBase, readBigUintLittleEndian } from './randomBase.js'; + +const seedRandomBlockSize = Int32Array.BYTES_PER_ELEMENT; + +export class SeedRandomWrapper extends RandomBase { + private rng: seedrandom.PRNG; + private buffer: Uint8Array; + private filledBuffer: Uint8Array; + constructor(seed: string | number) { + super(); + this.rng = seedrandom(seed.toString()); + this.buffer = new Uint8Array(seedRandomBlockSize); + this.filledBuffer = new Uint8Array(0); + } + private fillBuffer(): void { + this.buffer.fill(0); + this.buffer = this.fillBufferDirect(this.buffer); + this.filledBuffer = this.buffer; + } + private fillBufferDirect(buffer: Uint8Array): Uint8Array { + if ((buffer.length % seedRandomBlockSize) !== 0) throw new Error(`SeedRandomWrapper.fillBufferDirect should always be called with the buffer with the length a multiple-of-${seedRandomBlockSize}!`); + const length = buffer.length / seedRandomBlockSize; + const dataView = new DataView(buffer.buffer); + let byteOffset = 0; + for (let index = 0; index < length; index++, byteOffset += seedRandomBlockSize) { + dataView.setInt32(byteOffset, this.rng.int32(), false); + } + return buffer; + } + protected generateBigUintByBytes(bytes: number): bigint { + let u8a = new Uint8Array(Math.ceil(bytes / 8) * 8); + if (u8a.length < 1 || !Number.isSafeInteger(bytes)) return 0n; + u8a = this.generateBytes(u8a.subarray(0, bytes)); + return readBigUintLittleEndian(u8a.buffer) ?? 0n; + } + + public generateBigUintByBits(bits: number): bigint { + if (bits < 1 || !Number.isSafeInteger(bits)) return 0n; + const bytes = Math.ceil(bits / 8); + const wastedBits = BigInt(bytes * 8 - bits); + return this.generateBigUintByBytes(bytes) >> wastedBits; + } + + public generateBytes(array: Uint8Array): Uint8Array { + if (array.length < 1) return array; + array.fill(0); + let dst = array; + if (dst.length <= this.filledBuffer.length) { + dst.set(this.filledBuffer.subarray(0, dst.length)); + this.filledBuffer = this.filledBuffer.subarray(dst.length); + return array; + } else { + while (dst.length > 0) { + if (this.filledBuffer.length === 0) { + if (dst.length >= seedRandomBlockSize) { + const df64 = dst.subarray(0, dst.length - (dst.length % seedRandomBlockSize)); + this.fillBufferDirect(df64); + dst = dst.subarray(df64.length); + continue; + } + this.fillBuffer(); + } + if (dst.length <= this.filledBuffer.length) { + dst.set(this.filledBuffer.subarray(0, dst.length)); + this.filledBuffer = this.filledBuffer.subarray(dst.length); + return array; + } + dst.set(this.filledBuffer); + dst = dst.subarray(this.filledBuffer.length); + this.fillBuffer(); + } + return array; + } + } +} From eb7edd3d1969a0ac9f533fbc971f836d8a7e1095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:15:31 +0900 Subject: [PATCH 5/7] Updated `aiscript.api.md` --- etc/aiscript.api.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index 0593f82f..c430eb08 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -271,9 +271,6 @@ type Continue_2 = NodeBase_2 & { type: 'continue'; }; -// @public (undocumented) -function cryptoGen64(array: BigUint64Array): bigint | null; - declare namespace Cst { export { isStatement_2 as isStatement, @@ -861,9 +858,6 @@ export class Scope { }; } -// @public (undocumented) -function signedNumber32ToBigUint32(num: number): bigint; - // @public (undocumented) type Statement = Definition | Return | Each | For | Loop | Break | Continue | Assign | AddAssign | SubAssign; @@ -924,9 +918,6 @@ type TypeSource = NamedTypeSource | FnTypeSource; // @public (undocumented) type TypeSource_2 = NamedTypeSource_2 | FnTypeSource_2; -// @public (undocumented) -function unbiasedRandomIntegerInRange(min: number, max: number, generator: G, gen64: (generator: G) => bigint | null): number | null; - // @public (undocumented) const unWrapRet: (v: Value) => Value; @@ -950,10 +941,7 @@ declare namespace utils { valToJs, jsToVal, getLangVersion, - reprValue, - cryptoGen64, - signedNumber32ToBigUint32, - unbiasedRandomIntegerInRange + reprValue } } export { utils } From 26f0c3a4bfd5523970f6cbcb446ee48a8fd1928b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:24:45 +0900 Subject: [PATCH 6/7] * Removed `Math:rnd_unbiased` --- src/interpreter/lib/std.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts index 5dc7a5f3..ec6ccb2b 100644 --- a/src/interpreter/lib/std.ts +++ b/src/interpreter/lib/std.ts @@ -416,13 +416,6 @@ export const std: Record = { return NUM(CryptoGen.instance.generateNumber0To1()); }), - 'Math:rnd_unbiased': FN_NATIVE(([min, max]) => { - assertNumber(min); - assertNumber(max); - const res = CryptoGen.instance.generateRandomIntegerInRange(min.value, max.value); - return res === null ? NULL : NUM(res); - }), - 'Math:gen_rng': FN_NATIVE(([seed]) => { expectAny(seed); if (seed.type !== 'num' && seed.type !== 'str') return NULL; From 717629308a9b5326b60c18bd9f59d1fb55f69b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=A1=E3=83=BC=E3=81=9A=28=EF=BD=A58=EF=BD=A5=29?= =?UTF-8?q?=E3=81=91=E3=83=BC=E3=81=8D?= <31585494+MineCake147E@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:57:28 +0900 Subject: [PATCH 7/7] * Updated `CHANGELOG.md` * Updated `std-math.md` --- CHANGELOG.md | 2 ++ docs/std-math.md | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab62e6f9..c7b95a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Fix: チェイン系(インデックスアクセス`[]`、プロパティアクセス`.`、関数呼び出し`()`)と括弧を組み合わせた時に不正な挙動をするバグを修正 - 関数`Str#charcode_at` `Str#to_arr` `Str#to_char_arr` `Str#to_charcode_arr` `Str#to_utf8_byte_arr` `Str#to_unicode_codepoint_arr` `Str:from_unicode_codepoints` `Str:from_utf8_bytes`を追加 - Fix: `Str#codepoint_at`がサロゲートペアに対応していないのを修正 +- Fix: `Math:rnd`が範囲外の値を返す可能性があるのを修正 +- 関数`Math:gen_rng_unbiased`を追加 ## Note バージョン0.16.0に記録漏れがありました。 >- 関数`Str:from_codepoint` `Str#codepoint_at`を追加 diff --git a/docs/std-math.md b/docs/std-math.md index a25b7bec..32ce40eb 100644 --- a/docs/std-math.md +++ b/docs/std-math.md @@ -122,6 +122,11 @@ _min_ および _max_ を渡した場合、_min_ <= x, x <= _max_ の整数、 ### @Math:gen_rng(_seed_: num | str): fn シードから乱数生成機を生成します。 +生成された乱数生成器は 0 <= x, x < 1 の 小数を返します。 + +### @Math:gen_rng_unbiased(_seed_: num | str): @(_min_: num, _max_: num) +シードから _min_ <= x, x <= _max_ の整数を一様分布で生成する乱数生成機を生成します。 +_min_ および _max_ が渡されていない場合はエラーとなります。 ## その他 ### @Math:clz32(_x_: num): num