Преобразование 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 как строку и разбираю строку как двойную, я получаю то, что хочу:

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

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

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

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

 float f = 0.1F; double d = f; 

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

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

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

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

 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 

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

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

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

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

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

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

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

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

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

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

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

 float flt = 145.664454; Double dbl = 0.0; dbl += flt; 
  • Усечение Десятичное число не округлено
  • "F" после числа / float в Objective-C / C
  • Как преобразовать целое число в float в Java?
  • Клавиши с плавающей запятой в std: map
  • Почему изменение 0.1f to 0 замедляет производительность на 10x?
  • Деление с плавающей запятой против умножения с плавающей запятой
  • Сравнение с плавающей точкой
  • Составляют ли какие-либо JIT-компиляторы JVM код, который использует векторизованные инструкции с плавающей запятой?
  • Является ли добавление и умножение с плавающей запятой ассоциативным?
  • Операции с плавающей запятой в C-ассоциативном?
  • Вставить в int vs floor
  • Давайте будем гением компьютера.