Оператор 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, Всегда помните, что:

  1. doubleс или floats - это просто приближения действительных чисел, это просто рациональные числа, которые не одинаково распределены между
  2. Вы должны очень тщательно использовать арифметические операторы между doubleс или floats, которые не являются близкими (есть много других правил, таких как это...)
  3. в общем, никогда не используйте doubleс или float если вам нужна произвольная точность

Так как double является числовым типом с плавающей запятой, который является способом аппроксимации числовых значений. Представления с плавающей точкой кодируют числа так, что мы можем хранить числа, намного большие или меньшие, чем мы обычно могли бы. Однако не все числа могут быть представлены в данном пространстве, поэтому несколько чисел округляются до одного и того же значения с плавающей запятой.

В качестве упрощенного примера, мы можем захотеть хранить значения в диапазоне от -1000 до 1000 в небольшом объеме пространства, где мы обычно можем хранить только от -10 до 10. Таким образом, мы можем округлить все значения до ближайшей тысячи и хранить их в небольшом пространстве: -1000 кодируется как -10, -900 кодируется как -91000 кодируется как 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 не изменится.

Другие вопросы по тегам