JavaScript Integer matematické nesprávné výsledky

Jen se snažím implementovat jednoduchý RNG v JS.

Děje se tak, že javascript vyhodnotí 119106029 * 1103515245 být 131435318772912110 spíše než 131435318772912105 . Víme, že je to špatně, protože dvě lichá čísla vynásobená nedávají sudé číslo.

Ví někdo, co se děje? Chci jen spolehlivý opakovatelný RNG a kvůli těmto nesprávným hodnotám nemohu získat výsledky, které by odpovídaly mé implementaci C stejné věci.

Odpověď

Podle standardu ECMAScript jsou všechna čísla v JavaScriptu (64bitový IEEE 754) standardně čísly s pohyblivou řádovou čárkou.

Všechna 32bitová celá čísla však mohou být přesně reprezentována jako čísla s plovoucí desetinnou čárkou. Výsledek můžete vynutit na 32 bitů pomocí příslušného bitového operátoru, jako je tento:

x = (a * b) >>> 0;  // force to unsigned int32
x = (a * b) | 0;    // force to signed int32

Divné, ale takový je standard.

(Toto chování zaokrouhlování je mimochodem jednou z nejčastěji hlášených „chyb“ proti JavaScriptovému enginu Firefoxu. Vypadá to, že letos bylo hlášeno zatím třikrát…)

Pokud chcete skutečnou celočíselnou matematiku, můžete použít BigInt hodnoty, jiný typ čísla, zapsaný s n na konci:

> 119106029n * 1103515245n
131435318772912105n

Toto je relativně nedávná funkce JS a nemusí být implementována ve starých prohlížečích.

Pokud jde o reprodukovatelná náhodná čísla v JavaScriptu, benchmark V8 používá toto:

// To make the benchmark results predictable, we replace Math.random
// with a 100% deterministic alternative.
Math.random = (function() {
  var seed = 49734321;
  return function() {
    // Robert Jenkins' 32 bit integer hash function.
    seed = ((seed + 0x7ed55d16) + (seed << 12))  & 0xffffffff;
    seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
    seed = ((seed + 0x165667b1) + (seed << 5))   & 0xffffffff;
    seed = ((seed + 0xd3a2646c) ^ (seed << 9))   & 0xffffffff;
    seed = ((seed + 0xfd7046c5) + (seed << 3))   & 0xffffffff;
    seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
    return (seed & 0xfffffff) / 0x10000000;
  };
})();