Читайте также:
|
|
Для начала я хочу задать вопрос. Очень простой. Чему равна следующая сумма – 0.3f + 0.4f? Чему?0.7f? Проверим:
float f1 = 0.7f; float f2 = 0.3f + 0.4f;System.out.println("f1==f2: "+(f1==f2));Как результат? Нравится? Мне тоже. Для тех, кто не выполнил этот фрагмент, скажу – результат будет...
f1==f2: falseПочему это происходит?.. Выполним еще один тест:
float f1 = 0.3f; float f2 = 0.4f; float f3 = f1 + f2; float f4 = 0.7f;System.out.println("f1="+(double)f1);System.out.println("f2="+(double)f2);System.out.println("f3="+(double)f3);System.out.println("f4="+(double)f4);Обратите внимание на приведение к double. Это сделано для того, чтобы вывести побольше знаков после запятой.Результат:
f1=0.30000001192092896f2=0.4000000059604645f3=0.7000000476837158f4=0.699999988079071Собственно говоря, результат прогнозируемый. Представление дробной части осуществляется с помощью конечного ряда2-n, а потому о точном представлении произвольно взятого числа говорить не приходится. Как видно из примера,точность представления float – 7 знаков после запятой.
Строго говоря, в представлении float на мантиссу отведено 24 бита. Таким образом минимальное по модулю число, которое можно представить с помощью float (без учета степени, ибо мы говорим о точности) – это2-24≈6*10-8. Именно с таким шагом реально идут значения в представлении float.А поскольку есть квантование – есть и погрешность.
Отсюда вывод: числа в представлении float можно сравнивать только с определенной точностью. Я бы рекомендовал округлять их до 6-го знака после запятой (10-6), либо, что предпочтительнее, проверял бы абсолютное значение 2 разности между ними:
float f1 = 0.3f; float f2 = 0.4f; float f3 = f1 + f2; float f4 = 0.7f;System.out.println("|f3-f4|<1e-6: "+(Math.abs(f3-f4) < 1e-6));В этом случае результат вселяет надежду:
|f3-f4|<1e-6: trueРазумеется, точно та же картина и с типом double. С единственной разницей, что там на мантиссу отведено 53бита, следовательно, точность представления – 2-53≈10-16. Да, величина квантования куда меньше, но она есть. И может сыграть злую шутку.
Кстати, в тестовой библиотеке JUnit в методах сравнения вещественных чисел точность указывается в явном виде. Т.е. метод сравнения содержит три параметра – число, чему оно должно быть равно и точность сравнения.
Еще кстати, хочу упомянуть о тонкости, связаной с записью чисел в научном формате, с указанием степени.Вопрос. Как записать 10-6? Практика показывает, что более 80% отвечают – 10e-6. Между тем, правильный ответ – 1e-6! А 10e-6 – это 10-5! Мы наступили на эти грабли в одном из проектов,довольно неожиданно. Ошибку искали очень долго, на константы смотрели раз 20. И ни у кого не возникло ни тени сомнения в их правильности, пока однажды, в большой степени случайно, константу 10e-3 не вывели на печать и не обнаружили у нее после запятой два знака вместо ожидавшихся трех. А потому – будьте бдительны!
Движемся дальше.
И -0.0
В представлении вещественных чисел старший бит является знаковым. А что будет, если все остальные биты равны 0? В отличие от целых, где в такой ситуации получается отрицательное число, находящееся на нижней границе диапазона представления, вещественное число только со старшим битом, выставленным в 1, тоже обозначает 0,только со знаком минус. Таким образом, у нас есть два нуля – +0.0 и -0.0.
Возникает логичный вопрос – считать ли эти числа равными? Виртуальная машина считает именно так. Однако, это два разных числа, ибо в результате операций с ними получаются разные значения:
float f1 = 0.0f/1.0f; float f2 = 0.0f/-1.0f;System.out.println("f1="+f1);System.out.println("f2="+f2);System.out.println("f1==f2: "+(f1==f2)); float f3 = 1.0f / f1; float f4 = 1.0f / f2;System.out.println("f3="+f3);System.out.println("f4="+f4);... и результат:
f1=0.0f2=-0.0f1==f2: truef3=Infinityf4=-InfinityТаким образом, в некоторых случаях есть смысл расценивать +0.0 и -0.0 как два разных числа.А если у нас есть два объекта, в одном из которых поле равно +0.0, а в другом -0.0 – эти объекты точно так же можно расценивать как неравные. Возникает вопрос – а как понять, что числа неравны, если их прямое сравнение виртуальной машиной дает true?
Ответ таков. Несмотря на то, что виртуальнай машина считает эти числа равными, представления у них все-таки отличаются.Поэтому – единственное, что можно сделать, это сравнить представления. А для того, чтобы его получить, существуют методыint Float.floatToIntBits(float) и long Double.doubleToLongBits(double), которые возвращают битовое представление в виде int и long соответственно (продолжение предыдущего примера):
int i1 = Float.floatToIntBits(f1); int i2 = Float.floatToIntBits(f2);System.out.println("i1 (+0.0):"+ Integer.toBinaryString(i1));System.out.println("i2 (-0.0):"+ Integer.toBinaryString(i2));System.out.println("i1==i2: "+(i1 == i2));Результатом будет
i1 (+0.0):0i2 (-0.0):10000000000000000000000000000000i1==i2: falseТаким образом, если у вас +0.0 и -0.0 – разные числа, то сравнивать вещественные переменные следует через их битовое представление.
С +0.0 и -0.0 вроде как разобрались. -0.0, однако, является не единственным сюрпризом. Есть еще такое явление как...
Дата добавления: 2015-10-21; просмотров: 75 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Вещественный тип данных | | | Do something |