Почему сравнение совпадений float в Java?
class Test{
public static void main(String[] args){
float f1=3.2f;
float f2=6.5f;
if(f1==3.2){
System.out.println("same");
}else{
System.out.println("different");
}
if(f2==6.5){
System.out.println("same");
}else{
System.out.println("different");
}
}
}
выход:
different
same
Почему это так? Я ожидал same
как результат в первом случае.
Ответы
Ответ 1
Разница в том, что 6.5 можно представить точно как в float, так и в double - тогда как 3.2 не может быть точно представлена в любом типе... и два ближайших приближения различны. Сравнение равенства между float и double сначала преобразует float в double, а затем сравнивает два. Таким образом, потеря данных.
Вы не должны сравнивать поплавки или парные разряды для равенства; потому что вы не можете гарантировать, что число, которое вы назначаете на float или double, является точным. Эта ошибка округления является характерной особенностью вычисления с плавающей запятой.
Сжатие бесконечного числа действительных чисел в конечное число бит требует приблизительного представления. Хотя есть бесконечно много целых чисел, в большинстве программ результат целочисленных вычислений может храниться в 32 битах.
В отличие от этого, учитывая любое фиксированное количество бит, большинство вычислений с реальными числами будут давать количества, которые не может быть точно представлена с использованием этого количества бит. Следовательно результат вычисления с плавающей запятой часто должен быть округлен в порядке чтобы вернуться в свое конечное представление. Эта ошибка округления является характерная особенность вычисления с плавающей запятой.
Отметьте Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой больше!
Ответ 2
Они представляют собой реализацию различных частей стандарта IEEE с плавающей запятой. A float
имеет ширину 4 байта, а double
- 8 байтов.
Как правило, вы, вероятно, предпочтете использовать double
в большинстве случаев и использовать только float
, когда у вас есть веские основания. (Примером хорошей причины использовать float
в отличие от double
является "Я знаю, что мне не нужна такая точность, и мне нужно хранить миллион из них в памяти".) Также стоит отметить, что трудно доказать, что вам не нужна точность double
.
Кроме того, при сравнении значений с плавающей запятой для равенства вы обычно хотите использовать что-то вроде Math.abs(a-b) < EPSILON
, где a
и b
- сравниваемые значения с плавающей запятой, а EPSILON
- это небольшое значение с плавающей запятой как 1e-5
. Причиной этого является то, что значения с плавающей запятой редко кодируют точное значение, которое они должны "- скорее, они обычно кодируют значение очень близко - так что вам нужно" прищуриться ", когда вы определяете, совпадают ли два значения.
РЕДАКТИРОВАТЬ. Все должны прочитать ссылку @Kugathasan Abimaran, размещенную ниже: Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой для больше!
Ответ 3
Чтобы узнать, с чем вы имеете дело, вы можете использовать Float и Double метод toHexString:
class Test {
public static void main(String[] args) {
System.out.println("3.2F is: "+Float.toHexString(3.2F));
System.out.println("3.2 is: "+Double.toHexString(3.2));
System.out.println("6.5F is: "+Float.toHexString(6.5F));
System.out.println("6.5 is: "+Double.toHexString(6.5));
}
}
$ java Test
3.2F is: 0x1.99999ap1
3.2 is: 0x1.999999999999ap1
6.5F is: 0x1.ap2
6.5 is: 0x1.ap2
Как правило, число имеет точное представление, если оно равно A * 2 ^ B, где A и B - целые числа, допустимые значения которых задаются спецификацией языка (а double имеет более допустимые значения).
В этом случае,
6.5 = 13/2 = (1 + 10/16) * 4 = (1 + a/16) * 2 ^ 2 == 0x1.ap2, а
3.2 = 16/5 = (1 + 9/16 + 9/16 ^ 2 + 9/16 ^ 3 +...) * 2 ^ 1 == 0x1.999., p1.
Но Java может содержать только конечное число цифр, поэтому он сокращает 0,999., в какой-то момент. (Вы можете вспомнить из математики, что 0.999... = 1. Это в базе 10. В основании 16 это будет 0.fff.... = 1.)
Ответ 4
class Test {
public static void main(String[] args) {
float f1=3.2f;
float f2=6.5f;
if(f1==3.2f)
System.out.println("same");
else
System.out.println("different");
if(f2==6.5f)
System.out.println("same");
else
System.out.println("different");
}
}
Попробуйте это, и он будет работать. Без "f" вы сравниваете плавающий с другим плавающим типом и различной точностью, что может вызвать неожиданный результат, как в вашем случае.
Ответ 5
Невозможно напрямую сравнивать значения типа float
и double
. Перед сравнением значений необходимо либо преобразовать double
в float
, либо преобразовать float
в double
. Если сделать первое сравнение, преобразование спросит: "Имеет ли float
наилучшее представление float
значения double
?" Если сделать последнее преобразование, вопрос будет: "Имеет ли float
идеальное представление значения double
". Во многих контекстах первый вопрос является более значимым, но Java предполагает, что все сравнения между float
и double
предназначены для того, чтобы задать последний вопрос.
Я бы предположил, что независимо от того, какой язык готов терпеть, одни стандарты кодирования должны абсолютно положительно запрещать прямые сравнения между операндами типа float
и double
. С учетом кода:
float f = function1();
double d = function2();
...
if (d==f) ...
невозможно определить, какое поведение предполагается в случаях, когда d
представляет значение, которое не является точно представимым в float
. Если предполагается, что f
преобразуется в double
, и результат этого преобразования по сравнению с d
, следует написать сравнение как
if (d==(double)f) ...
Хотя приказ не меняет поведение кода, он ясно показывает, что поведение кода является преднамеренным. Если бы было намерение, что сравнение показывает, имеет ли f
лучшее float
представление d
, оно должно быть:
if ((float)d==f)
Обратите внимание, что поведение этого очень сильно отличается от того, что произойдет без трансляции. Если бы ваш исходный код применял операнд double
каждого сравнения к float
, тогда оба теста равенства прошли бы.
Ответ 6
В целом не рекомендуется использовать оператор == с числом с плавающей запятой из-за проблем аппроксимации.
Ответ 7
6.5 можно представить точно в двоичном формате, тогда как 3.2 не может. Поэтому разница в точности не имеет значения для 6.5, поэтому 6.5 == 6.5f
.
Чтобы быстро обновить, как работают двоичные числа:
100 -> 4
10 -> 2
1 -> 1
0.1 -> 0.5 (or 1/2)
0.01 -> 0.25 (or 1/4)
и др.
6.5 в двоичном формате: 110.1
(точный результат, остальные цифры равны нулю)
3.2 в двоичном формате: 11.001100110011001100110011001100110011001100110011001101...
(здесь точность имеет значение!)
Поплавок имеет только 24-битную точность (остальные используются для знака и экспонента), поэтому:
3.2f в двоичном формате: 11.0011001100110011001100
(не равное аппроксимации двойной точности)
В основном это то же самое, что при написании 1/5 и 1/7 в десятичных числах:
1/5 = 0,2
1,7 = 0,14285714285714285714285714285714.
Ответ 8
Float имеет меньшую точность, чем double, bcoz float использует 32 бит, в которых 1 используется для Sign, 23 точности и 8 для Exponent. Если в качестве двойного используется 64 бита, в которых 52 используется для точности, 11 для показателя и 1 для знака.... Точность важна. Десятичное число, представленное как float и double, может быть равно или неравномерно зависит от необходимости точности (т.е. Диапазона чисел после десятичной точки может меняться). С уважением С. ЗАКИР