Duże liczby błędnie zaokrąglone w JavaScript

Zobacz ten kod:

var jsonString = '{"id":714341252076979033,"type":"FUZZY"}';
var jsonParsed = JSON.parse(jsonString);
console.log(jsonString, jsonParsed);

Kiedy widzę moją konsolę w Firefoksie 3.5, wartość {[2] } jest zaokrąglona:

Object id=714341252076979100 type=FUZZY

Próbowałem różnych wartości ,ten sam wynik (liczba zaokrąglona).

Nie rozumiem też zasad zaokrąglania. 714341252076979136 zaokrągla się do 714341252076979200, natomiast 714341252076979135 zaokrągla się do 714341252076979100. Dlaczego to się dzieje?
Author: double-beep, 2009-09-04

6 answers

To, co tu widzisz, jest efektem dwóch zaokrągleń. Liczby w Ecmascripcie są wewnętrznie reprezentowane jako zmiennoprzecinkowe o podwójnej precyzji. Gdy id jest ustawione na 714341252076979033 (0x9e9d9958274c359 W hex), w rzeczywistości jest przypisana najbliższa reprezentowalna wartość podwójnej precyzji, która jest 714341252076979072 (0x9e9d9958274c380). Po wydrukowaniu wartości jest ona zaokrąglana do 15 znaczących cyfr dziesiętnych, co daje 14341252076979100.

 69
Author: Stephen Canon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-10 17:52:59

W tym celu należy wykonać następujące czynności: Te dokumenty muszą być sznurkami.

IEEE-754 double-precision zmiennoprzecinkowy (rodzaj liczby, której używa JavaScript) nie może dokładnie reprezentować wszystkich liczb (oczywiście). / Align = "center" bgcolor = "# e0ffe0 " / cesarz Chin / / align = center / To może wpływać na liczby całkowite, tak jak wpływa na liczby ułamkowe; zaczyna się, gdy osiągniesz powyżej 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER).

Beyond Number.MAX_SAFE_INTEGER + 1 (9007199254740992), na Format zmiennoprzecinkowy IEEE-754 nie może już reprezentować każdej kolejnej liczby całkowitej. 9007199254740991 + 1 jest 9007199254740992, ale 9007199254740992 + 1 jest także 9007199254740992 ponieważ 9007199254740993 nie może być reprezentowany w formacie. Następny, który może być 9007199254740994. Wtedy 9007199254740995 nie może być, ale 9007199254740996 może.

Powodem jest to, że skończyły nam się bity, więc nie mamy już bitu 1s; bit najniższego rzędu reprezentuje teraz wielokrotności 2. W końcu, jeśli będziemy kontynuować, tracimy ten bit i pracujemy tylko w wielokrotnościach 4. I tak on

Twoje wartości są dobrze powyżej tego progu, a więc są zaokrąglane do najbliższej reprezentowalnej wartości.

Od ES2020 możesz użyć BigInt dla liczb całkowitych, które są dowolnie duże, ale nie ma dla nich reprezentacji JSON. Możesz użyć ciągów i funkcji reviver:

const jsonString = '{"id":"714341252076979033","type":"FUZZY"}';
// Note it's a string −−−−^−−−−−−−−−−−−−−−−−−^

const obj = JSON.parse(jsonString, (key, value) => {
    if (key === "id" && typeof value === "string" && value.match(/^\d+$/)) {
        return BigInt(value);
    }
    return value;
});

console.log(obj);
(Look in the real console, the snippets console doesn't understand BigInt.)

Jeśli jesteś ciekawy bitów, oto co się dzieje: binarna Liczba zmiennoprzecinkowa IEEE-754 o podwójnej precyzji ma bit znaku, 11 bitów wykładnika (który określa ogólną skalę liczby, jako moc 2 [ponieważ jest to format binarny]) i 52 bity significand (ale format jest tak sprytny, że z tych 52 bitów otrzymuje 53 bity precyzji). Sposób użycia wykładnika jest skomplikowany ( opisany tutaj ), ale w bardzo niejasnych terminach, jeśli dodamy jeden do wykładnika, wartość significand jest podwojona, ponieważ wykładnik jest używany dla potęg 2 (ponownie, uwaga, nie jest bezpośredni, jest tam spryt).

Spójrzmy więc na wartość 9007199254740991 (aka, Number.MAX_SAFE_INTEGER):

   +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit
  / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent
 / /        |  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand
/ /         | /                                                  |
0 10000110011 1111111111111111111111111111111111111111111111111111
                = 9007199254740991 (Number.MAX_SAFE_INTEGER)

Ta wartość wykładnicza, 10000110011, oznacza, że za każdym razem, gdy dodamy jeden do significand, reprezentowana liczba wzrasta o 1 (liczba całkowita 1, straciliśmy możliwość reprezentowania liczb ułamkowych znacznie wcześniej).

/ Align = "left" / Aby przejść obok tej liczby, musimy zwiększyć wykładnik, co oznacza, że jeśli dodamy jeden do significand, wartość liczby reprezentowana jest przez 2, a nie 1 (ponieważ wykładnik jest zastosowany do 2, podstawy tej binarnej liczby zmiennoprzecinkowej):
   +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit
  / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent
 / /        |  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand
/ /         | /                                                  |
0 10000110100 0000000000000000000000000000000000000000000000000000
                = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)
W porządku, bo i tak jest. Ale! Nie możemy reprezentować 9007199254740993. Skończyły nam się kawałki. Jeśli dodamy tylko 1 do significand, to dodamy 2 do wartości:
   +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit
  / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent
 / /        |  +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand
/ /         | /                                                  |
0 10000110100 0000000000000000000000000000000000000000000000000001
                = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)

Format nie może już reprezentować liczb nieparzystych, ponieważ zwiększamy wartość, wykładnik jest zbyt duży.

W końcu znów zabraknie nam bitów i musimy zwiększyć wykładnik, więc możemy reprezentować tylko wielokrotności 4. Następnie wielokrotności 8. Następnie wielokrotność 16. I tak dalej.

 62
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-06 09:29:31

To nie jest spowodowane przez ten parser json. Po prostu spróbuj wpisać 714341252076979033 do konsoli fbug. Zobaczysz ten sam 714341252076979100.

Zobacz ten wpis na blogu po szczegóły: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

 9
Author: thorn̈,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-04 15:31:40

JavaScript używa wartości zmiennoprzecinkowych o podwójnej precyzji, tj.]}

ceil(lb 714341252076979033) = 60

Bity dokładnie reprezentujące wartość.

Najbliższą dokładnie reprezentowalną liczbą jest 714341252076979072 (Zapisz oryginalną liczbę w binarnym, zamień ostatnie 7 cyfr na 0 i zaokrągl w górę, ponieważ najwyższą zastąpioną cyfrą było 1).

Otrzymasz 714341252076979100 zamiast tej liczby, ponieważ ToString() zgodnie z opisem ECMA-262, §9.8.1 działa z potęgami dziesięciu i w 53 bitach precyzja wszystkie te liczby są równe.

 5
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-04 16:38:12

Problem polega na tym, że Twój numer wymaga większej precyzji niż JavaScript.

Czy możesz wysłać numer jako ciąg znaków? Rozdzielone na dwie części?

 4
Author: Esteban Küber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-05 15:19:20

JavaScript może obsłużyć tylko dokładne liczby całkowite do około 9000 milionów milionów (czyli 9 z 15 zerami). Wyżej i dostaniesz śmieci. Obejmij to, używając ciągów do trzymania liczb. Jeśli potrzebujesz zrobić matematykę z tymi liczbami, napisz własne funkcje lub sprawdź, czy możesz znaleźć dla nich bibliotekę: proponuję tę pierwszą, ponieważ nie lubię bibliotek, które widziałem. Aby zacząć, zobacz dwie z moich funkcji w another answer .

 2
Author: Robert L,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:30