Number.prototype.toFixed: удивительно правильно в Internet Explorer
Учтите следующее:
var x = 2.175;
console.log(x.toFixed(2)); // 2.17
Какие? Нет, здесь нет ничего удивительного. Это довольно очевидно, см.: число буквальное 2.175
на самом деле хранится в памяти (по правилам IEEE-754) как значение, чуть чуть меньше 2,175. И это легко доказать:
console.log(x.toFixed(20)); // 2.17499999999999982236
Вот как это работает в последних версиях Firefox, Chrome и Opera на 32-битной установке Windows. Но это не вопрос.
Реальный вопрос заключается в том, как Internet Explorer 6 (!) На самом деле удается это сделать право как люди
var x = 2.175;
console.log(x.toFixed(2)); // 2.18
console.log(x.toFixed(20)); // 2.17500000000000000000
Хорошо, я слишком драматизировал: на самом деле все Internet Explorer, на которых я это тестировал (IE8-11 и даже MS Edge!), Ведут себя одинаково. Все еще, WAT?
ОБНОВЛЕНИЕ: Это становится незнакомым:
x=1.0;while((x-=0.1) > 0) console.log(x.toFixed(20));
IE Chrome
0.90000000000000000000 0.90000000000000002220
0.80000000000000000000 0.80000000000000004441
0.70000000000000010000 0.70000000000000006661
0.60000000000000010000 0.60000000000000008882
0.50000000000000010000 0.50000000000000011102
0.40000000000000013000 0.40000000000000013323
0.30000000000000015000 0.30000000000000015543
0.20000000000000015000 0.20000000000000014988
0.10000000000000014000 0.10000000000000014433
0.00000000000000013878 0.00000000000000013878
Почему разница - во всем, кроме последнего? И почему нет разницы в последнем? Это очень похоже на x=0.1; while(x-=0.01)...
Кстати, пока мы не приблизимся к нулю, toFixed
в IE видимо пытается срезать некоторые углы.
Отказ от ответственности: я знаю, что математика с плавающей точкой является своего рода ошибкой.Я не понимаю, в чем разница между IE и остальным миром браузеров.
3 ответа
Указанное поведение отличается от требований спецификации ECMA.
В соответствии с пунктом 8.5 Number
Тип имеет 64-разрядные двоичные значения IEEE-754, за исключением того, что существует только один NaN. Таким образом, 2.175 не может быть представлено точно; самый близкий, который Вы можете получить, является 2.17499999999999982236431605997495353221893310546875.
Согласно 15.7.4.5, toFixed(20)
использует алгоритм, который сводится к:
- "Пусть n будет целым числом, для которого точное математическое значение n ÷ 10f - x максимально близко к нулю. Если есть два таких n, выберите большее n".
- Выше f равно 20 (количество запрошенных цифр), а x является операндом, который должен быть 2.17499999999999982236431605997495353221893310546875.
- Это приводит к выбору 217499999999999982236 для n.
- Затем n форматируется, создавая "2.17499999999999982236".
Я ценю вклад Эрика, но, при всем уважении, он не отвечает на вопрос. Я признаю, что был слишком насмешлив с этими "правильными" и "удивительно правильными" фразами; но да, я понимаю, что поведение IE на самом деле является отклонением.
Тем не мение. Я все еще искал объяснение того, что заставляет IE вести себя по-другому - и я наконец-то получил нечто похожее на подсказку... по иронии судьбы, в трекере Mozilla, в этой длительной дискуссии. Цитата:
OUTPUT IN MOZILLA:
a = 0.827 ==> a.toFixed(17) = 0.82699999999999996
b = 1.827 ==> b.toFixed(17) = 1.82699999999999996
OUTPUT IN IE6:
a = 0.827 ==> a.toFixed(17) = 0.82700000000000000
b = 1.827 ==> b.toFixed(17) = 1.82700000000000000
Разница в IE и Mozilla заключается в следующем. IE хранит "a" в виде строки, а Mozilla хранит "a" в качестве значения. Спецификация не фиксирует формат хранения. Таким образом, когда IE делает
a.toFixed
он начинается с точного строкового представления, в то время как Mozilla переносит туда-обратно преобразования.
Было бы здорово получить какое-то официальное подтверждение по этому поводу, но, по крайней мере, это объясняет все, что я видел до сих пор. Особенно,
console.log( 0.3.toFixed(20) ); // 0.30000000000000000000
console.log( 0.2.toFixed(20) ); // 0.20000000000000000000
console.log( (0.3 - 0.2).toFixed(20) ); // "0.09999999999999998000"
Прежде всего, числа с плавающей точкой не могут быть "точно" представлены в двоичных числах. Там будет повышение / депрессия, либо значение будет немного выше или немного ниже. То, насколько это повышено / понижено, зависит от того, как выполняется преобразование. Не существует "правильного значения" даже для строкового вывода из ECMAScript toFixed()
,
Но стандарты ECMA делают вещи более прямыми, устанавливая стандарты. Что, на мой взгляд, хорошо. Это как "Если мы все равно будем делать ошибки, давайте сделаем то же самое".
Таким образом, теперь возникает вопрос, как и почему IE отклоняется от стандартов. Давайте рассмотрим следующие тестовые случаи.
Кандидатами являются IE 10.0.9200.16688 и Chrome 30.0.1599.69, работающие на 64-разрядной версии Windows 8 Pro.
Case Code IE (10) Chrome (30)
--------------------------------------------------------------------------------
A (0.06).toFixed(20) 0.60000000000000000000 0.05999999999999999778
B (0.05+0.01).toFixed(20) 0.06000000000000000500 0.06000000000000000472
Итак, независимо от того, IE или Chrome, мы видим (0.06)
не совсем равен (0.05+0.01)
, Это почему? Это потому, что (0,06) имеет представление, которое очень близко, но не равно (0,06), так же как и (0,05) и (0,01). Когда мы выполняем операцию, такую как сложение, очень незначительные ошибки могут суммироваться, чтобы стать ошибкой немного другой величины.
Теперь разница в отображаемом значении в разных браузерах может быть затронута по двум причинам:
- Используется алгоритм преобразования.
- Когда происходит конвертация.
Теперь мы не знаем, какой алгоритм использует IE, так как я не могу заглянуть в его источник. Но приведенные выше тестовые примеры ясно демонстрируют еще одну вещь: IE и Chrome обрабатывают преобразование "не только по-разному", но и "по другому случаю".
В JavaScript, когда мы создаем число (или экземпляр Number
класс с или без new
ключевое слово), мы на самом деле предоставляем literal
, Литерал всегда является строкой, даже если он обозначает число [1]. Браузер анализирует литерал, создает объект и присваивает представленное значение.
Теперь, где вещи, как правило, идут разными путями. IE задерживает преобразование, пока оно не понадобится. Это означает, что до тех пор, пока не произойдет операция, IE будет сохранять число как буквальное (или некоторый промежуточный формат). Но Chrome сразу же преобразует его в рабочий формат.
После выполнения операции IE не возвращается к буквальному или промежуточному формату, поскольку это бессмысленно и может привести к небольшой потере точности.
Я надеюсь, что это проясняет что-то.
[1] Значения, представленные в коде, всегда literal
s. Если вы цитируете их, они называются String Literal
s.