Преобразование float в double без потери точности

У меня есть примитивный поплавок, и мне нужно как примитивный двойной. Простое литье поплавка, чтобы удвоить, дает мне странную дополнительную точность. Например:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

Однако, если вместо кастинга я вывожу float как строку и разбираю строку как double, я получаю то, что хочу:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

Есть ли лучший способ, чем перейти к String и обратно?

Ответ 1

Дело не в том, что вы на самом деле получаете дополнительную точность - это то, что поплавок не точно представлял число, к которому вы изначально стремились. Двойник точно представляет исходное значение с плавающей точкой; toString показывает "лишние" данные, которые уже присутствовали.

Например (и эти цифры не верны, я просто придумываю) предположим, что вы имели:

float f = 0.1F;
double d = f;

Тогда значение f может быть ровно 0.100000234523. Значение d будет точно таким же, но когда вы преобразуете его в строку, оно "поверит" в точность с более высокой точностью, поэтому не округлится так рано, и вы увидите "лишние цифры", которые были уже там, но скрыто от вас.

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

Вы уверены, что float/double являются подходящими типами для использования вместо BigDecimal? Если вы пытаетесь использовать числа с точными десятичными значениями (например, деньги), тогда BigDecimal является более подходящим типом IMO.

Ответ 2

Я считаю, что преобразование в двоичное представление легче понять эту проблему.

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

Вы можете видеть, что float расширяется до double, добавляя 0s к концу, но двойное представление 0.27 является "более точным", следовательно проблема.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

Ответ 3

Это связано с контрактом Float.toString(float),, который гласит:

Сколько цифр должно быть напечатано для дробная часть [& hellip;]? Там должно быть не менее одной цифры для представляют дробную часть и кроме этого, как много, , но только так много,больше цифр, необходимых для однозначного отличить значение аргумента от смежные значения типа float. Что Предположим, что x - точное математическое значение, представленное десятичное представление, созданное этот метод для конечного ненулевого аргумент f. Тогда f должно быть поплавком значение, ближайшее к x; или, если два поплавка значения одинаково близки к x, то f должен быть одним из них и наименьшим значительная часть значимости f должно быть 0.

Ответ 4

Сегодня я столкнулся с этой проблемой и не мог использовать рефакторинг для BigDecimal, потому что проект действительно огромный. Однако я нашел решение, используя

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

И это работает.

Обратите внимание, что вызов result.doubleValue() возвращает 5623.22998046875

Но вызов doubleResult.doubleValue() возвращается правильно 5623.23

Но я не совсем уверен, правильно ли это решение.

Ответ 5

Используйте BigDecimal вместо float/double. Существует множество чисел, которые не могут быть представлены как двоичная с плавающей запятой (например, 0.1). Таким образом, вы либо должны всегда округлять результат до известной точности, либо использовать BigDecimal.

Подробнее см. http://en.wikipedia.org/wiki/Floating_point.

Ответ 6

Я нашел следующее решение:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Если вы используете float и double вместо Float и Double, используйте следующее:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}

Ответ 7

Поплавки по своей природе неточны и всегда имеют аккуратные округлые "проблемы". Если точность важна, вы можете рассмотреть возможность рефакторинга приложения для использования Decimal или BigDecimal.

Да, поплавки вычисляются быстрее, чем десятичные числа из-за поддержки на процессоре. Однако вы хотите быстро или точно?

Ответ 8

Для информации это относится к пункту 48 - Избегайте float и double, когда требуются точные значения, из Effective Java 2nd edition от Джошуа Блоха. Эта книга - варенье, наполненное хорошими вещами, и, безусловно, стоит посмотреть.

Ответ 9

Это работает?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;

Ответ 10

Если вам нужно сделать это с обертками:

Double d = new Float(123f).doubleValue();