Преобразование поплавка в двойное без потери точности - PullRequest
86 голосов
/ 27 мая 2009

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

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

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

Ответы [ 10 ]

113 голосов
/ 27 мая 2009

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

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

float f = 0.1F;
double d = f;

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

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

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

32 голосов
/ 07 февраля 2012

Я считаю, что преобразование в двоичное представление проще для понимания этой проблемы.

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)));

Вы можете видеть, что число с плавающей запятой расширяется до двойного, добавляя 0 к концу, но что двойное представление 0,27 является «более точным», поэтому проблема.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
24 голосов
/ 27 мая 2009

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

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

13 голосов
/ 02 января 2013

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

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

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

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

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

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

7 голосов
/ 27 мая 2009

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

См. http://en.wikipedia.org/wiki/Floating_point для получения дополнительной информации.

5 голосов
/ 25 января 2017

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

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();
}
1 голос
/ 27 мая 2009

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

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

0 голосов
/ 27 сентября 2018

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

Double d = new Float(123f).doubleValue();
0 голосов
/ 04 марта 2016

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

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
0 голосов
/ 27 мая 2009

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

...