Преобразование поплавка в двойное без потери точности

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

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

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

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

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

12 ответов

Решение

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

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

float f = 0.1F;
double d = f;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Простое решение, которое хорошо работает, - это проанализировать двойное значение из строкового представления числа с плавающей запятой:

double val = Double.valueOf(String.valueOf(yourFloat));

Не очень эффективно, но работает!

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

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

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

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

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;

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

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

Существует способ преобразовать значение Float в Double без добавления дополнительной точности.

      Float aFloat= new Float(0.11);
String s = aFloat.toString();
Double aDouble = Double.parseDouble(s);

Этот подход не добавит дополнительной точности к вашему значению Float при преобразовании. Единственная проблема с этим подходом заключается в использовании памяти JVM путем создания дополнительного объекта tamp String.

При вызове toString() (aDouble.toString()) для Double никогда не будет добавлена ​​дополнительная точность. Точность будет добавлена ​​при преобразовании типа.

Другие вопросы по тегам