Когда вы изучаете 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 для точных вычислений.