Преобразуйте число с плавающей запятой в двойное без потери точности.

StackOverflow https://stackoverflow.com/questions/916081

  •  06-09-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

Дело не в том, что ты на самом деле получение дополнительной точности - это то, что число с плавающей точкой не совсем точно представляет число, к которому вы изначально стремились.Двойной является точное представление исходного поплавка; 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.

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

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

Видеть http://en.wikipedia.org/wiki/Floating_point Чтобы получить больше информации.

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

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Если вы используете плавать и двойной вместо Плавать и Двойной используйте следующее:

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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top