Парсинг двойных значений с использованием Double против BigDecimal в Java

Я опытный разработчик, но не математик. Я знаю достаточно о спецификации IEEE с плавающей запятой, чтобы бояться делать предположения о разборе, печати и сравнении их.

Я знаю, что могу разобрать double из String, используя Double.parseDouble(String s). Я знаю, что могу также проанализировать эту же строку в BigDecimal с помощью new BigDecimal(String s), а затем запросить у BigDecimal double с помощью BigDecimal.doubleValue().

Я взглянул на API и код для обоих методов, и кажется, что BigDecimal имеет много разных вариантов анализа и преобразования.

Гарантированы ли оба метода (Double.parseDouble(s) и new BigDecimal(s).doubleValue()) для всех строковых входов, чтобы они производили одно и то же примитивное значение double, при условии, что значение не находится вне диапазона плюс или минус Double.MAX_DOUBLE?

Ответы

Ответ 1

Для большинства входных значений оба метода должны давать одинаковые значения. Хотя все еще возможно, что они не могут, это не кажется вероятным.

BigDecimal(String) конструктор Javadocs утверждает:

API Note:

Для значений, отличных от float и double NaN и ± Infinity, этот конструктор совместим со значениями, возвращаемыми Float.toString(float) и Double.toString(double).

Однако метод Double.parseDouble(String) гласит:

Возвращает новый double, инициализированный значением, представленным указанным String, как выполняется методом valueOf класса Double.

И это продолжает описывать формат, принятый методом.

Пусть это проверят!

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

public static void main(String[] args)
{
    String[] values = {"0", "0.1", "0.33333333333333333333", "-0", "-3.14159265", "10.1e100",
            "0.00000000000000000000000000000000000000000000000000142857142857",
            "10000000000.000000000000000001", "2.718281828459",
            "-1.23456789e-123", "9.87654321e+71", "66666666.66666667",
            "1.7976931348623157E308", "2.2250738585072014E-308", "4.9E-324",
            "3.4028234663852886E38", "1.1754943508222875E-38", "1.401298464324817E-45",
            String.valueOf(Math.E), String.valueOf(Math.PI), String.valueOf(Math.sqrt(2))
    };
    for (String value : values) {
        System.out.println(isDoubleEqual(value));
    }
}
// Test if the representations yield the same exact double value.
public static boolean isDoubleEqual(String s) {
    double d1 = Double.parseDouble(s);
    double d2 = new BigDecimal(s).doubleValue();
    return d1 == d2;
}

За эти значения я получаю все true с. Это ни в коем случае не является исчерпывающим, поэтому было бы очень трудно доказать, что это верно для всех возможных значений double. Все, что нужно, это один false, чтобы показать контрпример. Однако это, кажется, является некоторым доказательством того, что это верно для всех допустимых строковых представлений double.

Я также пробовал ведущие пробелы, например " 4". Конструктор BigDecimal(String) выбросил NumberFormatException, но Double.parseDouble правильно обрезал ввод.

Конструктор BigDecimal(String) не будет принимать Infinity или NaN, но вы спрашивали только о нормальном конечном диапазоне. Метод Double.parseDouble принимает шестнадцатеричные представления с плавающей запятой, но BigDecimal(String) - нет.

Если вы включите эти крайние случаи, один метод может вызвать исключение, а другой - нет. Если вы ищете обычные строки из 10 с конечными значениями в пределах диапазона, ответ "кажется вероятным".

Ответ 2

  Гарантированы ли оба метода (Double.parseDouble(s) и new BigDecimal(s).doubleValue()) для всех строковых входов, чтобы получить точно такое же значение двойного примитива, при условии, что значение не находится вне диапазона плюс или минус Double.MAX_DOUBLE?

Нет, конечно нет.

Во-первых, есть некоторые строковые входы, которые поддерживает Double.parseDouble(s), но new BigDecimal(s) не поддерживает (например, шестнадцатеричные литералы).

С другой стороны, Double.parseDouble("-0") дает отрицательный ноль, тогда как new BigDecimal("-0").doubleValue() дает положительный ноль (потому что BigDecimal не имеет понятия нулевого знака).

И хотя это не имеет прямого отношения к вашему вопросу, меня попросили указать в интересах других читателей, что Double.parseDouble(s) поддерживает NaN и бесконечности, тогда как BigDecimal нет.