При работе с вещественными типами в Java есть несколько граблей, часть из них, не самые страшные, уже были упомянуты в предыдущей статье.
Теперь рассмотрим несколько других, наиболее интересных :) Которые не часто освещаются в книга по программированию на Java и соответственно часто вызывают недоумения у начинающих программистов и не только на Java, поскольку это связано с представлением чисел с плавающей точкой в процессорах.
Чтобы сразу стало понятно о чём пойдет речь, разберем простой пример. Как вы уже должны знать, вещественные литералы в Java, по умолчанию имеют тип double. Теперь допустим у нас есть вот такая строка:
double d = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
То есть мы 10 раз складываем число 0.1. Можно предположить что в результате получится 1, но как бы не так. Мы получим число очень близкое к единице но не единицу, например это может быть число:
0.9999999999999999
Если же мы этот же эксперимент проделаем для типа float:
float f = 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f;
то там будет другой результат:
1.0000001
Это объясняется разностью в точности представления вещественных чисел в типах double и float в двоичном формате (вспоминаем, что компьютер работает только с нулями и единицами :) ).
Более подробно и популярно почему так происходит можно почитать тут и тут. Чистая теори о представлении вещественных чисел тут.
Из этого следует, что вещественные числа обычно не сравнивают оператором ==, а проверяют что модуль разности двух чисел меньше какого-то маленького числа (погрешности вычислений).
Еще одними грабельками при вычислениях с вещественными числами может быть ситуация когда возможно подобрать такое положительное число Х при котором будет верно сравнение a+x == x, то есть прибавление какого-то числа к переменной не меняет ее значения.
Чтобы стало все понятнее приведу пример:
import static pro.java.util.Print.*; | |
public class FloatingPoint02 { | |
public static void main(String[] args) { | |
double d = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1; | |
float f = 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f; | |
println("d= " + d); | |
println("f= " + f); | |
println("---------"); | |
println("0.35-0.33 = " + (0.35 + 0.33)); | |
println("---------"); | |
double d01 = 0.1; | |
double d00 = 0; | |
for (int i = 0; i < 10; i++) { | |
d00 += d01; | |
println("Итерация " + (i + 1) + " " + d00); | |
} | |
println("---------"); | |
double d1 = .1; | |
double d2 = .2; | |
double d3 = .3; | |
println("d1 = " + d1 + " d2= " + d2 + " d3= " + d3); | |
println("Прямое сравнение"); | |
println("d1 + d2 = d3 " + ((d1 + d2) == d3)); | |
println("Проверка на допустимую погрешность – 0.0001"); | |
println("d1 + d2 = d3 " + (Math.abs(d1 + d2 - d3) < 1E-4)); | |
double dOne = 1.0; | |
int count = 0; | |
println("Пример когда d+x == d"); | |
for (double dd = 0; dd <= 4.9E-323; dd = Math.nextUp(dd)){ | |
if ((dOne + dd)== dOne ){ // вот это интересно :) | |
println("dd = " + dd); | |
count++; | |
} | |
} | |
println("Итого в заданном промежутке есть " + count + " чисел"); | |
println("при суммировании с которыми dOne будет равно dOne :)"); | |
double dPlus = 4.9E-324; | |
println("Например dPlus = " + dPlus + " dOne = " + dOne); | |
println("dOne + dPlus = " + (dPlus + dOne)); | |
dOne = dOne + dPlus; | |
println("dOne = dOne + dPlus и после этого dOne == " + dOne); | |
} | |
} |
Вывод программы:
Приведу еще один интересный пример слева и его вывод ниже:
Как видим, в первом случае dd и ff не равны друг другу. Такое происходит при сужающем преобразовании.
Затем мы сделали наоборот и теперь dd равно ff. Весело, не правда ли? :)
Именно из-за такого поведения double и float не используют в финансовых расчётах.
И еще парочка статей на эту тему: раз и два.
Ну и на последок, очень хорошая статья на эту тему. А BigInteger и BigDecimal, рассматриваемые в ней, мы изучим чуть позже.
Комментариев нет:
Отправить комментарий