Когда вы изучаете JavaScript, вас предупреждают, что 0.1 + 0.2 !== 0.3
и все это знают, верно? Он равен 0.30000000000004
, и это не ошибка JavaScript, это делает ваш процессор. Так что кодирование на Java или любом другом языке не поможет.
Но знаете ли вы, как часто это происходит? Решил проверить, ха-ха. Как проводился этот эксперимент: я создал сценарий, который перебирал все числа от 0,01 до 100,00 и выполнял операции -, +, *
и /
.
Сумма
Эта ошибка возникает в 22% операций SUM.
5.33 + 5.2 === 10.530000000000001 7.84 + 4.28 === 12.120000000000001 11.92 + 207.85 === 219.76999999999998 99.52 + 6.27 === 105.78999999999999 939.92 + 153.49 === 1093.4099999999999 843.32 + 131.47 === 974.7900000000001 36067.29 + 3920.23 === 39987.520000000004 ....
Разница
В 56,6% операций DIFF мы получаем «бонус»:
8.13 - 5.75 === 2.380000000000001 8.93 - 4.4 === 4.529999999999999 6.09 - 3.43 === 2.6599999999999997 4.95 - 2.82 === 2.1300000000000003 986.83 - 93.44 === 893.3900000000001 119.93 - 43.35 === 76.58000000000001 490.01 - 10.91 === 479.09999999999997 122.72 - 6.43 === 116.28999999999999 ....
Умножение
Такое случается:
- в 17% операций, когда вы выполняете [целое число] * [с плавающей запятой]
- в 36% случаев, когда вы используете [с плавающей точкой] * [с плавающей точкой]
- в 0%, когда вы сделаете [целое число] * [целое число] (это ожидается)
8.38 * 0.3 === 2.5140000000000002 9.16 * 8.22 === 75.29520000000001 3.37 * 3.33 === 11.222100000000001 9.68 * 8.22 === 79.56960000000001 89.86 * 9.46 === 850.0756000000001 73.85 * 7.81 === 576.7684999999999 21.39 * 1.27 === 27.165300000000002 80.04 * 8.66 === 693.1464000000001 71.64 * 4.64 === 332.40959999999995
Дивизион
Как часто это бывает?
- в 16,4% операций при использовании [с плавающей точкой] / [целое число]
- в 26% операций при использовании [целое число] / [с плавающей запятой]
- в 36% случаев, когда вы используете [с плавающей точкой] / [с плавающей точкой]
- в 0%, когда вы делаете целое / целое число
99.27 / 3 == 33.089999999999996 (should be 33.09) 57.3 / 3 == 19.099999999999998 (should be 19.1) 73.15 / 7 == 10.450000000000001 (should be 10.45) 58.2 / 3 == 19.400000000000002 (should be 19.4) 69.96 / 3 == 23.319999999999997 (should be 23.32) 32.76 / 9 == 3.6399999999999997 (should be 3.64) 28.62 / 3 == 9.540000000000001 (should be 9.54)
parseFloat
parseFloat никогда не ошибается. Он никогда не вернет 54.5999999999994
, если вы передадите ему строку 54.6
. Возвращает точно54.6
parseFloat('54.6') // 54.6 parseFloat('123.45678901') // 123.45678901 parseFloat('54.599999999999994') // 54.599999999999994
Всегда ли «a + b + c» равно «c + b + a»?
Неа! Ваш учитель математики сказал вам это? Видеть:
85.13 + 5.96 + 8.44 === 99.52999999999999 8.44 + 5.96 + 85.13 === 99.53 94.4 + 7.12 + 4.67 === 106.19000000000001 4.67 + 7.12 + 94.4 === 106.19 43.57 + 5.33 + 3.05 === 51.949999999999996 3.05 + 5.33 + 43.57 === 51.95
Резюме
Не надейтесь, что ошибки с плавающей запятой будут редкими. Они встречаются в 17–56% всех математических операций, поэтому существует очень вероятность того, что вы столкнетесь с этой ошибкой.
Когда вы пишете модульные тесты или проводите ручное тестирование, используйте числа, которые я поделился здесь. И в комментариях расскажите, сколько багов нашли :)
Я поделился здесь другими примерами, чтобы вы могли использовать их в своих модульных тестах: https://github.com/ellenaua/floating-point-error-examples/tree/master/examples
Как этого избежать?
Используйте numeral.js
или decimal.js
для точных вычислений.