Преобразование 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 и обратно?
- Как мне сделать сравнение с плавающей запятой?
- Как вы печатаете EXACT значение числа с плавающей запятой?
- Усечение Десятичное число не округлено
- Когда следует использовать double вместо десятичного?
- Преобразовать десятичное число в двойное?
- Должны ли мы обычно использовать float-литералы для поплавков вместо простых двойных литералов?
- Как я могу написать функцию питания самостоятельно?
- Почему деление двух целых чисел возвращает 0.0 в Java?
- Имеет ли float отрицательный ноль? (-0f)
- Формат плавающей точки для std :: ostream
- Почему Double.MIN_VALUE не является отрицательным?
- Алгоритм преобразования двоичного кода IEEE 754 в строку?
- Как определить число с плавающей запятой, используя регулярное выражение
Дело не в том, что вы на самом деле получаете дополнительную точность – это то, что поплавок не точно отражает число, на которое вы стремились первоначально. Двойник точно представляет исходный поплавок; 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;