Large numbers erroneously rounded in JavaScript
Смотрите этот код:
<html>
<head>
<script src="http://www.json.org/json2.js" type="text/javascript"></script>
<script type="text/javascript">
var jsonString = '{"id":714341252076979033,"type":"FUZZY"}';
var jsonParsed = JSON.parse(jsonString);
console.log(jsonString, jsonParsed);
</script>
</head>
<body>
</body>
</html>
Когда я вижу свою консоль в Firefox 3.5, значение jsonParsed равно:
Object id=714341252076979100 type=FUZZY
Т.е. число округлено. Пробовал разные значения, один и тот же результат (число округлено).
Я также не понимаю правила округления. 714341252076979136 округляется до 714341252076979200, тогда как 714341252076979135 округляется до 714341252076979100.
РЕДАКТИРОВАТЬ: см. Первый комментарий ниже. По-видимому, речь идет не о JSON, а об обработке чисел в JavaScript. Но остается вопрос:
Почему это происходит?
6 ответов
То, что вы видите здесь, на самом деле является эффектом двух округлений. Числа в ECMAScript являются внутренне представленными с плавающей точкой двойной точности. когда id
установлен в 714341252076979033
(0x9e9d9958274c359
в шестнадцатеричном виде) ему фактически присваивается ближайшее представимое значение двойной точности, которое 714341252076979072
(0x9e9d9958274c380
). Когда вы распечатываете значение, оно округляется до 15 значащих десятичных цифр, что дает 14341252076979100
,
Вы переполняете возможности числового типа JavaScript, подробнее см. §8.5 спецификации. Эти идентификаторы должны быть строками.
IEEE-754 с плавающей запятой двойной точности (тип числа, используемого JavaScript) не может точно представлять все числа (конечно). Классно, 0.1 + 0.2 == 0.3
ложно Это может повлиять на целые числа так же, как это влияет на дробные числа; начинается, когда вы превысите 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER
).
за Number.MAX_SAFE_INTEGER + 1
(9007199254740992
), формат с плавающей точкой IEEE-754 больше не может представлять каждое последовательное целое число. 9007199254740991 + 1
является 9007199254740992
, но 9007199254740992 + 1
также 9007199254740992
так как 9007199254740993
не может быть представлен в формате. Следующее, что может быть 9007199254740994
, затем 9007199254740995
не может быть, но 9007199254740996
Можно.
Причина в том, что у нас закончились биты, поэтому у нас больше нет бита 1с; бит самого младшего порядка теперь представляет кратные 2. В конечном итоге, если мы продолжим работу, мы потеряем этот бит и будем работать только с кратными 4. И так далее.
Ваши значения намного выше этого порога, и поэтому они округляются до ближайшего представимого значения.
Если вам интересно узнать о битах, вот что происходит: двоичное число IEEE-754 с двойной точностью с плавающей запятой имеет знаковый бит, 11 битов показателя степени (который определяет общую шкалу числа как степень 2 [ потому что это двоичный формат]), и 52 бита значимости (но формат настолько умный, что из этих 52 бит получается 53 бита точности). Как используется показатель степени, сложно ( описано здесь), но в очень расплывчатых терминах, если мы добавим единицу к показателю степени, значение значимое удваивается, так как показатель степени используется для степеней 2 (опять же, будьте осторожны, это не прямой, там есть хитрость).
Итак, давайте посмотрим на значение 9007199254740991
(Он же, Number.MAX_SAFE_INTEGER
):
+ ------------------------------------------------- --- --- знак бит / + --- --- + --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- экспонента / / | + ------------------------------------------------- + - значимо / / | / | 0 10000110011 1111111111111111111111111111111111111111111111111111 = 9007199254740991 (Number.MAX_SAFE_INTEGER)
Это показатель степени, 10000110011
, означает, что каждый раз, когда мы добавляем единицу к значению, представленное число увеличивается на 1 (целое число 1, мы потеряли способность представлять дробные числа гораздо раньше).
Но теперь это значение полно. Чтобы пройти это число, мы должны увеличить показатель степени, что означает, что если мы добавим единицу к значению and, значение представленного числа возрастет на 2, а не на 1 (поскольку показатель степени применяется к 2, основание этого двоичное число с плавающей запятой):
+ ------------------------------------------------- --- --- --- бит знака --- --- экспонента ///- | + ------------------------------------------------- + - значимый / / | / | 0 10000110100 0000000000000000000000000000000000000000000000000000 = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)
Ну, это нормально, потому что 9007199254740991 + 1
является 9007199254740992
тем не мение. Но! Мы не можем представлять 9007199254740993
, У нас кончились биты. Если мы добавляем только 1 к значению, это добавляет 2 к значению:
+ ------------------------------------------------- --- --- знак бит / + --- --- + --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- экспонента / / | + ------------------------------------------------- + - значимо / / | / | 0 10000110100 0000000000000000000000000000000000000000000000000001 = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)
Формат просто не может больше представлять нечетные числа, так как мы увеличиваем значение, показатель степени слишком велик.
В конце концов, у нас снова заканчиваются значимые и битовые числа, и нам приходится увеличивать показатель степени, поэтому в итоге мы можем представить только кратные 4. Затем кратные 8. Затем кратные 16. И так далее.
Это не вызвано этим парсером json. Просто попробуйте ввести 714341252076979033 в консоль fbug. Вы увидите то же самое 714341252076979100.
Смотрите этот пост в блоге для деталей: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too
JavaScript использует значения с плавающей запятой двойной точности, то есть общую точность 53 бита, но вам нужно
ceil(lb 714341252076979033) = 60
биты, чтобы точно представлять значение.
Ближайшее точно представимое число 714341252076979072
(запишите оригинальный номер в двоичном коде, замените последние 7 цифр на 0
и округлить, потому что самая высокая замененная цифра была 1
).
Ты получишь 714341252076979100
вместо этого числа, потому что ToString()
как описано в ECMA-262, §9.8.1 работает с степенями десяти, и с точностью до 53 бит все эти числа равны.
Проблема в том, что ваш номер требует большей точности, чем JavaScript.
Можете ли вы отправить номер в виде строки? Разделить на две части?
JavaScript может обрабатывать только целые числа примерно до 9000 миллионов (это 9 с 15 нулями). Выше, и вы получите мусор. Обойти это, используя строки для хранения чисел. Если вам нужно выполнить математику с этими числами, напишите свои собственные функции или посмотрите, сможете ли вы найти для них библиотеку: я предлагаю первую, поскольку мне не нравятся библиотеки, которые я видел. Чтобы начать, посмотрите две мои функции в другом ответе.