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

#java

#java #значение с плавающей запятой #double

Вопрос:

У меня есть примитивный float, и мне нужен примитивный double. Простое преобразование 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:

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

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

 float f = 0.1F;
double d = f;
  

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

При преобразовании в строку и обратно вы получаете значение double, которое ближе к строковому значению, чем исходное значение float, но это хорошо только в том случае, если вы действительно верите, что строковое значение — это то, что вы действительно хотели.

Вы уверены, что 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) , в котором частично говорится:

Сколько цифр должно быть напечатано для дробной части […]? Для представления дробной части должна быть по крайней мере одна цифра, а сверх этого должно быть столько, но только столько,
еще цифр, сколько необходимо для однозначного отличия значения аргумента от соседних значений типа 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

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

Комментарии:

1. У меня сработало. Также выполняется очень быстро. Не уверен, почему это не помечено как ответ.

2. Это метод, который я использую, и вам не нужна новая часть Float… Просто создайте FloatingDecimal с помощью примитива. Он будет автоматически упакован в коробку… И работает тоже быстро… Есть on; y один недостаток: FloatingDecimal неизменяем, поэтому вам нужно создавать по одному для каждого отдельного float … представьте вычислительный код O (1e10)!!! Конечно, BigDecimal имеет тот же недостаток…

3. Недостатком этого решения является то, что sun.misc. FloatingDecimal — это внутренний класс JVM, и сигнатура его конструктора была изменена в Java 1.8. Никто не должен использовать внутренние классы в реальном приложении.

Ответ №5:

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

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

Ответ №6:

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

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

Комментарии:

1. Допустим, у вас в кэше 10 миллионов торговых цен с плавающей запятой, вы все еще рассматриваете возможность использования BigDecimal и создания уникальных экземпляров, скажем, ~ 6 миллионов объектов (чтобы снизить минимальный вес / неизменяемость).. или в этом есть что-то еще?

2. @Sendi_t Пожалуйста, не используйте комментарии, чтобы задавать сложные вопросы 🙂

3. Спасибо, что посмотрели!.. мы используем float прямо сейчас, как это было согласовано десять лет назад — я пытался узнать вашу точку зрения на эту ситуацию в follow up — . спасибо!

4. @Sendi_t Недоразумение. Я не могу ответить на ваш вопрос в 512 символах. Пожалуйста, задайте правильный вопрос и пришлите мне ссылку.

5. @GKFX Не существует «единого размера для всех», когда дело доходит до обработки десятичных дробей на компьютерах. Но поскольку большинство людей этого не понимают, я указываю им на BigDecimal , поскольку это приведет к обнаружению большинства распространенных ошибок. Если работа идет слишком медленно, им нужно узнать больше и найти способы оптимизировать свои проблемы. Преждевременная оптимизация — корень всего зла — Д.Э. Кнут.

Ответ №7:

Простое решение, которое хорошо работает, заключается в разборе double из строкового представления float:

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

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

Ответ №8:

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

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

Комментарии:

1. Десятичная арифметика тоже неточна. (например 1 / 3 * 3 == 0,999999999999999999999999999999) Это, конечно, лучше для представления точных десятичных величин, таких как деньги, но для физических измерений у этого нет преимуществ.

2. Но 1 == 0,999999999999999999999999999999 🙂

Ответ №9:

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

Ответ №10:

Работает ли это?

 float flt = 145.664454;

Double dbl = 0.0;
dbl  = f<
  

Ответ №11:

Существует способ преобразовать значение 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 никогда не добавит дополнительных уточнений. Уточнения будут добавлены при преобразовании типа.