Метод java.lang.Double.parseValue обрабатывает странно созданные представления двойников непоследовательным образом.

Если вы пишете очень большое число, настолько большое, что оно выходит за пределы диапазона double, но затем добавляете большой отрицательный показатель, чтобы вернуть его в диапазон, вы попадаете в диапазон (проиллюстрирован здесь в REPL в Scala) :

scala>
java.lang.Double.parseDouble("10000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000001e-400")
res25: Double = 1.0E-21

С другой стороны, если вы напишете очень маленькое число, настолько маленькое, что оно выходит за пределы диапазона double, но затем используете большую положительную экспоненту, чтобы вернуть его в диапазон, это сработает, только если сама экспонента не слишком большой:

scala> 
java.lang.Double.parseDouble("0.000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000001e400")
res26: Double = Infinity

scala>
java.lang.Double.parseDouble("0.000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000001e200")
res27: Double = 1.0E-179

Это просто ошибка, или где-то есть спецификация, которая разрешает такое поведение, или все это разрешено спецификацией, и нужно просто благодарить свое благословение, когда получишь правильный результат? (Если это ошибка, была ли она исправлена?)

(Кроме того: я писал собственный код String-to-double и собирался отложить реализацию Java по умолчанию для сложных случаев, но этот тестовый пример не удался.)

23
Rex Kerr 18 Май 2014 в 19:43

3 ответа

Лучший ответ

Его почти наверняка нет в спецификации. Соответствующий раздел о с плавающей запятой Литералы в JLS определяют только значения литералов с плавающей запятой. Но это не говорит о их действительных представлениях .

Конечно, должны быть пределы. Никто не ожидал, что строка вроде

String s = "0.00... (3 billion zeros) ...001e3000000000";

Для анализа на 1.0. Но очевидно, что здесь пределы намного ниже.

Этот пример показывает предел:

public class DoubleTest
{
    public static void main(String[] args)
    {
        runTest(300, 324);
        runTest(300, 325);
        runTest(300, 326);
    }

    private static void runTest(int negativeExponent, int exponent)
    {
        String s = prefix(negativeExponent)+"1e"+exponent+"D";
        double d = Double.parseDouble(s);
        System.out.println(
            "For 1e-"+negativeExponent+" * 1e"+exponent+" result is "+d);
    }

    private static String prefix(int negativeExponent)
    {
        StringBuilder sb = new StringBuilder("0.");
        for (int i=0; i<negativeExponent; i++)
        {
            sb.append("0");
        }
        return sb.toString();
    }
}

Он печатает

Для 1e-300 * 1e324 результат 9.999999999999999E22

Для 1e-300 * 1e325 результат 1.0E24

Для 1e-300 * 1e326 результат Infinity

Фактически, это в первую очередь связано с используемой экспонентой . Соответствующая часть, которая вызывает эту помощь, находится в FloatingDecimal.java, строка 1996.

10
Community 20 Июн 2020 в 09:12

Это ошибка, но, глядя на реализацию, Oracle может сказать, что она как задумано .

Как это реализовано заключается в том, что ведущие kDigits преобразуются в длинное целое число. Таким образом, в первом примере вычисленная экспонента находится в пределах диапазона Double и успешно возвращает результат, если он попадает в этот диапазон.

Во втором случае показатель степени больше, чем максимальный десятичный показатель степени и вернет бесконечность.

В третьем случае это будет добраться сюда и вернет ожидаемый результат.

Хотя приведенные выше ссылки указывают на исходный код для OpenJDK 6, маловероятно, что они затронули рассматриваемый источник для JDK 7 и 8.

Анализ двойников традиционно был развлечением. Никаких сюрпризов от странностей в этом случае.

9
Donal Fellows 18 Май 2014 в 21:26

Я думаю, что это крайний случай, но тоже ошибка. Более простой пример:

String text = "0.000000000000000001e326";
System.out.println(Double.parseDouble(text));
System.out.println(new BigDecimal(text).doubleValue());

Который печатается в Java 7 (обновление 25) и Java 8 (обновление 5)

Infinity
1.0E308

Анализ BigDecimal и преобразование в double показывает, что это число представимо.

14
Peter Lawrey 18 Май 2014 в 17:50