Оператор Java == на двойниках
Этот метод возвращает 'true'. Зачем?
public static boolean f() {
double val = Double.MAX_VALUE/10;
double save = val;
for (int i = 1; i < 1000; i++) {
val -= i;
}
return (val == save);
}
8 ответов
Вы вычитаете довольно маленькое значение (менее 1000) из огромного значения. Небольшое значение настолько меньше, чем большое значение, что наиболее близким представимым значением к теоретическому результату остается исходное значение.
В основном это результат работы чисел с плавающей запятой.
Представьте, что у нас есть некоторый десятичный тип с плавающей запятой (просто для простоты), который хранит только 5 значащих цифр в мантиссе и показатель степени в диапазоне от 0 до 1000.
Ваш пример подобен написанию 10999 - 1000... подумайте, каков будет результат, если округлить до 5 значащих цифр. Да, точный результат - 99999..... 9000 (с 999 цифрами), но если вы можете представить значения только с 5 значащими цифрами, ближайший результат снова будет 10999.
Когда вы установите val
Double.MAX_VALUE/10, для него установлено значение, приблизительно равное 1.7976931348623158 * 10^307
, вычитание значений типа 1000 из этого потребовало бы точности в двойном представлении, что невозможно, поэтому в основном это оставляет val
без изменений.
В зависимости от ваших потребностей, вы можете использовать BigDecimal
вместо double
,
Double.MAX_VALUE
настолько велика, что JVM не показывает разницу между ним и Double.MAX_VALUE-1000
если вычесть число меньше, чем "1.9958403095347198E292" из Double.MAV_VALUE
результат все еще Double.MAX_VALUE
,
System.out.println(
new BigDecimal(Double.MAX_VALUE).equals( new BigDecimal(
Double.MAX_VALUE - 2.E291) )
);
System.out.println(
new BigDecimal(Double.MAX_VALUE).equals( new BigDecimal(
Double.MAX_VALUE - 2.E292) )
);
Ouptup:
правда
ложный
Дабл не обладает достаточной точностью для выполнения вычислений, которые вы пытаетесь выполнить. Таким образом, результат совпадает с начальным значением.
Это не имеет ничего общего с ==
оператор.
val
большое число и при вычитании 1
(или даже 1000
) из этого, результат не может быть должным образом выражен как double
значение. Представление этого числа x
а также x-1
то же самое, потому что double
имеет только ограниченное количество бит для представления неограниченного количества чисел.
Double.MAX_VALUE
это огромное количество по сравнению с 1 или 1000. Double.MAX_VALUE-1
как правило, равна Double.MAX_VALUE
, Таким образом, ваш код примерно ничего не делает при вычитании 1 или 1000 в Double.MAX_VALUE/10
, Всегда помните, что:
double
с илиfloat
s - это просто приближения действительных чисел, это просто рациональные числа, которые не одинаково распределены между- Вы должны очень тщательно использовать арифметические операторы между
double
с илиfloat
s, которые не являются близкими (есть много других правил, таких как это...) - в общем, никогда не используйте
double
с илиfloat
если вам нужна произвольная точность
Так как double
является числовым типом с плавающей запятой, который является способом аппроксимации числовых значений. Представления с плавающей точкой кодируют числа так, что мы можем хранить числа, намного большие или меньшие, чем мы обычно могли бы. Однако не все числа могут быть представлены в данном пространстве, поэтому несколько чисел округляются до одного и того же значения с плавающей запятой.
В качестве упрощенного примера, мы можем захотеть хранить значения в диапазоне от -1000 до 1000 в небольшом объеме пространства, где мы обычно можем хранить только от -10 до 10. Таким образом, мы можем округлить все значения до ближайшей тысячи и хранить их в небольшом пространстве: -1000 кодируется как -10
, -900 кодируется как -9
1000 кодируется как 10
, Но что, если мы хотим сохранить -999? Ближайшее значение, которое мы можем закодировать, равно -1000, поэтому мы должны закодировать -999 как то же значение, что и -1000: -10
,
В действительности схемы с плавающей запятой намного сложнее, чем в примере выше, но концепция похожа. Представления чисел с плавающей точкой могут представлять только некоторые из всех возможных чисел, поэтому, когда у нас есть число, которое не может быть представлено как часть схемы, мы должны округлить его до ближайшего представимого значения.
В вашем коде все значения в пределах 1000 Double.MAX_VALUE / 10
автоматически округляться до Double.MAX_VALUE / 10
вот почему компьютер думает (Double.MAX_VALUE / 10) - 1000 == Double.MAX_VALUE / 10
,
Результат вычисления с плавающей запятой является наиболее близким представимым значением к точному ответу. Эта программа:
public class Test {
public static void main(String[] args) throws Exception {
double val = Double.MAX_VALUE/10;
System.out.println(val);
System.out.println(Math.nextAfter(val, 0));
}
}
печатает:
1.7976931348623158E307
1.7976931348623155E307
Первым из этих чисел является ваш оригинальный val. Второй самый большой дубль, который меньше его.
Когда вы вычитаете 1000 из 1.7976931348623158E307, точный ответ находится между этими двумя числами, но очень, очень намного ближе к 1.7976931348623158E307, чем к 1.7976931348623155E307, поэтому результат будет округлен до 1.7976931348623155E307, в результате чего значение val не изменится.