Як дізнатись, чи може BigDecimal точно перетворити у плаваючий чи подвійний?


10

Клас BigDecimalмає кілька корисних методів для гарантування конверсії без втрат:

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

Однак методів так floatValueExact()і doubleValueExact()не існує.

Я читаю вихідний код OpenJDK для методів floatValue()і doubleValue(). І те, і інше, здається, є резервним Float.parseFloat()і Double.parseDouble()відповідно, що може повернути позитивну або негативну нескінченність. Наприклад, розбір рядка 10000 9s поверне позитивну нескінченність. Як я розумію, BigDecimalнемає внутрішнього поняття нескінченності. Далі, розбір рядка 100 9s як doubleдає 1.0E100, що не є нескінченним, але втрачає точність.

Що таке розумна реалізація floatValueExact()та doubleValueExact()?

Я думав про doubleвирішення шляхом об'єднання BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)і Double.toString(double), але це виглядає неакуратно. Я хочу запитати тут, бо може (треба!) Бути більш простим рішенням.

Щоб було зрозуміло, мені не потрібно високоефективне рішення.


7
Я думаю, ви могли б перетворити в подвійне, потім перетворити подвійний на BigDecimal, і побачити, чи отримаєте ви те саме значення. Не впевнений, у чому полягає випадок використання. Якщо ви використовуєте парні, ви вже приймаєте неточні значення. Якщо ви хочете точних значень, тоді залишайтеся з BigDecimal.
JB Nizet

Відповіді:


6

Від читання в документації , все це робить з numTypeValueExactваріантами, щоб перевірити існування дробової частини або якщо значення занадто велике для числового типу і кидати виключення.

Що стосується floatValue()і doubleValue()подібна перевірка переповнення робиться, але замість того , щоб викинути виняток, замість цього він повертає Double.POSITIVE_INFINITYабо Double.NEGATIVE_INFINITYдля двійників і Float.POSITIVE_INFINITYчи Float.NEGATIVE_INFINITYдля поплавців.

Тому найбільш розумна (і найпростіша) реалізація exactметодів для float і double, повинна просто перевірити, чи повертається конверсія POSITIVE_INFINITYабо NEGATIVE_INFINITY.


Крім того , пам’ятайте, що BigDecimalбуло розроблено для усунення недостатньої точності, що виникає внаслідок використання floatабо doubleдля великих ірраціоналів, тому, як прокоментував @JB Nizet , ще однією перевіркою, яку ви можете додати до вищезазначеного, було б перетворення або повернення, щоб побачити, чи все-таки ви отримаєте однакове значення. Це повинно довести, що конверсія була правильною.doublefloatBigDecimal

Ось як виглядатиме такий метод floatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!Float.isInfinite(result)) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

Використання compareToзамість equalsвищезазначеного є навмисним, щоб не стати занадто суворим з чеками. equalsбуде істинним лише тоді, коли два BigDecimalоб'єкти мають однакове значення та масштаб (розмір частки дробу в десятковій частині), тоді як compareToбуде помічено цю різницю, коли це не має значення. Так , наприклад 2.0проти 2.00.


Дуже хитрий. Саме те, що мені було потрібно! Примітка. Ви також можете використовувати Float.isFinite()або Float.isInfinite(), але це необов'язково. :)
kevinarpe

3
Також слід віддати перевагу порівнянню до над рівним, тому що рівняння () у BigDecimal вважатиме 2.0 та 2.00 різними значеннями.
JB Nizet

Значення, які неможливо точно представити як float, не працюватимуть. Приклад: 123.456f. Я думаю, це пов’язано з різними розмірами significand (mantissa) між 32-бітовим плаваючим і 64-бітним подвійним. У вашому коді вище, я отримую кращі результати з: if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {. Це розумна зміна ... чи я пропускаю інший кутовий випадок значень з плаваючою комою?
kevinarpe

1
@kevinarpe - 123.456fконцептуально такий же, як ваш приклад 1.0E100. Мене вражає те, що якщо вам потрібно перевірити, чи точно точне перетворення з BigDecimal -> бінарної плаваючої точки цьому і полягає проблема; тобто конверсію не слід розглядати.
Стівен C
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.