Перетворіть float у double, не втрачаючи точності


97

У мене є примітивний поплавок, і мені це потрібно як примітивний двійник. Просто кидання поплавка вдвічі дає мені дивну додаткову точність. Наприклад:

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

Чи є кращий спосіб, ніж поїхати до Стринга і назад?

Відповіді:


124

Це не те, що ви насправді отримуєте додаткову точність - це те, що поплавок не точно відображав число, на яке ви прагнули спочатку. Двічі в точно представляє оригінальний поплавець; toStringпоказує "зайві" дані, які вже були в наявності.

Наприклад (а ці цифри неправильні, я просто все вигадую) припустимо, у вас було:

float f = 0.1F;
double d = f;

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

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

Ви впевнені, що float / double є відповідними типами для використання тут замість BigDecimal? Якщо ви намагаєтесь використовувати цифри з точними десятковими значеннями (наприклад, гроші), то BigDecimalце більш відповідний тип IMO.


40

Я вважаю, що перетворення в двійкове представлення легше зрозуміти цю проблему.

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

Ви можете бачити, як поплавок розширюється до подвійного, додаючи 0s до кінця, але що подвійне представлення 0,27 є "більш точним", отже, проблема.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

25

Це пов’язано з контрактом Float.toString(float), який частково говорить:

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


13

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

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

І це працює.

Зверніть увагу, що виклик result.doubleValue () повертає 5623.22998046875

Але виклик doubleResult.doubleValue () повертає правильно 5623.23

Але я не зовсім впевнений, чи це правильне рішення.


1
Працювали для мене. Це також дуже швидко. Не знаю, чому це не позначено як відповідь.
Sameer

Це метод, який я використовую, і вам не потрібна нова частина Float ... Просто створіть FloatingDecimal за допомогою примітиву. Він буде автоматично вставлений в коробку ... І також працює швидко ... Є, є; y один недолік, FloatingDecimal незмінний, тому вам потрібно створити по одному для кожного окремого плаваючого ... уявіть обчислювальний код O (1e10)! !! Звичайно, BigDecimal має той самий недолік ...
Мостафа Зейналі,

6
Недоліком цього рішення є те, що sun.misc.FloatingDecimal - це внутрішній клас JVM, і підпис його конструктора була змінена в Java 1.8. Ніхто не повинен використовувати внутрішні заняття в реальному застосуванні.
Ігор Бляхін

8

Я знайшов таке рішення:

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

7

Використовуйте BigDecimalзамість float/ double. Є багато чисел, які неможливо представити як двійкові числа з плаваючою комою (наприклад, 0.1). Отже, ви завжди повинні округляти результат до відомих точних результатів або використовувати BigDecimal.

Для отримання додаткової інформації див. Http://en.wikipedia.org/wiki/Floating_point .


Припустимо, у вас є 10 мільйонів плаваючих торгових цін у кеш-пам’яті, ви все-таки розглядаєте можливість використання BigDecimal і створюєте унікальні екземпляри, скажімо, ~ 6 мільйонів об’єктів (щоб знизити масу ваги / незмінну) .. або є ще щось таке?
Sendi_t

1
@Sendi_t Будь ласка, не використовуйте коментарі, щоб задавати складні запитання :-)
Аарон Дігулла

Дякуємо за пошук! .. ми використовуємо плаваючі засоби прямо зараз, як це було узгоджено десять років тому - я намагався побачити вашу точку зору на цю ситуацію в подальшому -. Дякую!
Sendi_t

@Sendi_t Непорозуміння. Я не можу відповісти на ваше запитання 512 символами. Будь ласка, поставте відповідне запитання та надішліть мені посилання.
Аарон Дігулла

1
@GKFX Не існує "єдиного розміру для всіх", коли справа стосується обробки десяткових знаків на комп'ютерах. Але оскільки більшість людей цього не розуміють, я вказую їм, BigDecimalоскільки це вловлює більшість типових помилок. Якщо справи занадто повільні, їм потрібно дізнатися більше та знайти способи оптимізації своїх проблем. Передчасна оптимізація - корінь усього зла - Д. Е. Кнут.
Аарон Дігулла,

1

Поплавки за своєю природою неточні і завжди мають акуратні округлі "проблеми". Якщо точність важлива, тоді ви можете розглянути можливість рефакторингу програми для використання десяткової або великої десяткової.

Так, плаваючі обчислювальні швидше, ніж десяткові, завдяки підтримці вбудованого процесора. Однак, ви хочете швидко або точно?


1
Десяткова арифметика теж неточна. (наприклад, 1/3 * 3 == 0,9999999999999999999999999999), звичайно, краще представляти точні десяткові величини, як гроші, але для фізичних вимірювань це не має переваг.
dan04

2
Але 1 == 0,999999999999999999999999999999 :)
Еммануель Бур

0

Для інформації це стосується пункту 48 - Уникайте плавання та подвоєння, коли потрібні точні значення, Ефективного Java 2-го видання Джошуа Блоха. Ця книга є варенням, упакованим гарними речами, і, безумовно, варто подивитися.



0

Просте рішення, яке добре працює, - проаналізувати подвійне від представлення рядка поплавця:

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

Не надто ефективно, але це працює!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.