Преобразование 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();