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?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
.
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).
+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− 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.
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
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.
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?
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 .
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