Проблема с Java BigDecimal.remainder () - нерациональное и неточное возвращение - PullRequest
2 голосов
/ 24 марта 2020

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

Я использую BigDecimal, чтобы я мог работать с точными результатами, но два из моих девяти методов BigDecimal.remainder () возвращают иррациональные и неточные значения (остальные семь работают нормально), что делает мой конечный результат неточным ,

Например, если входное значение равно 70,70 и 100, на выходе будет одна дополнительная монета в 5 центов.

Я выделил проблему в своем коде. Любая помощь очень ценится. Заранее спасибо.

import java.math.BigDecimal;
import java.math.*;
import java.util.Scanner;

public class SP010Main {

    public static void main(String[] args) {

        // Declaring the values of notes and coins
        BigDecimal fifty = new BigDecimal(50.0);
        BigDecimal twenty = new BigDecimal(20.0);
        BigDecimal ten = new BigDecimal(10.0);
        BigDecimal five = new BigDecimal(5.0);
        BigDecimal two = new BigDecimal(2.0);
        BigDecimal one = new BigDecimal(1.0);
        BigDecimal fiftyC = new BigDecimal(0.5);
        BigDecimal twentyC = new BigDecimal(0.2);
        BigDecimal tenC = new BigDecimal(0.1);
        BigDecimal fiveC = new BigDecimal(0.05);


        // Getting input of cost and $ received
        Scanner scanner = new Scanner(System.in);
        System.out.print("Cost: ");
        BigDecimal cost = scanner.nextBigDecimal();
        System.out.print("$ Received: ");
        BigDecimal received = scanner.nextBigDecimal();
        BigDecimal totalC = received.subtract(cost);
        System.out.println("Total change returned: " + totalC);


        // Checking how many of each value is needed
        BigDecimal fiftyI = (totalC.divide(fifty, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(fifty);
        BigDecimal twentyI = (totalC.divide(twenty, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(twenty);
        BigDecimal tenI = (totalC.divide(ten, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(ten);
        BigDecimal fiveI = (totalC.divide(five, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(five);
        BigDecimal twoI = (totalC.divide(two, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(two);
        BigDecimal oneI = (totalC.divide(one, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(one);
        BigDecimal fiftyCI = (totalC.divide(fiftyC, 0, RoundingMode.FLOOR));
        totalC = totalC.remainder(fiftyC);

        // What should be happening with the problem----------------------------
        // E.g. if input is 70.70 and 100,
        // Following line will return 0.30
        System.out.println(totalC);
        // ---------------------------------------------------------------------


        BigDecimal twentyCI = (totalC.divide(twentyC, 0, RoundingMode.FLOOR));


        // The problem ---------------------------------------------------------
        // E.g. if input is 70.70 and 100,
        // Following outputs will both be 0.0999999999999999888977697537484345
        // 95763683319091796875
        totalC = totalC.remainder(twentyC);
        System.out.println(totalC);
        BigDecimal tenCI = (totalC.divide(tenC, RoundingMode.FLOOR));
        totalC = totalC.remainder(tenC);
        System.out.println(totalC);
        // End of the problem --------------------------------------------------    


        BigDecimal fiveCI = (totalC.divide(fiveC, 0, RoundingMode.FLOOR));

        // Display output
        System.out.printf("$50: %.0f \n$20: %.0f \n$10: %.0f \n$5: %.0f \n$2: %.0f \n$1: %.0f \n$0.50: %.0f \n$0.20: %.0f \n$0.10: %.0f \n$0.05: %.0f \n",fiftyI, twentyI, tenI, fiveI, twoI, oneI, fiftyCI, twentyCI, tenCI, fiveCI);

    }

}

Ответы [ 2 ]

2 голосов
/ 24 марта 2020

Даже если вы работаете с BigDecimal объектами для получения точных результатов, вы вводите неточность при объявлении объектов, представляющих купюры и монеты:

BigDecimal fifty = new BigDecimal(50.0);
BigDecimal twenty = new BigDecimal(20.0);
// ...

Значения, передаваемые в Конструктор интерпретируется как double, но некоторые из них не могут быть точно записаны в 64-битном формате двойной точности.

Вместо этого следует использовать конструктор на основе String:

BigDecimal fifty = new BigDecimal("50.0");
BigDecimal twenty = new BigDecimal("20.0");
// ...

Это даст вам правильный вывод:

Cost: 70.70
$ Received: 100
Total change returned: 29.30
0.30
0.10
0.00
$50: 0 
$20: 1 
$10: 0 
$5: 1 
$2: 2 
$1: 0 
$0.50: 0 
$0.20: 1 
$0.10: 1 
$0.05: 0 
0 голосов
/ 24 марта 2020

Никогда не используйте конструктор BigDecimal(double). Как говорит javado c:

Результаты этого конструктора могут быть несколько непредсказуемыми.

Прочитайте javado c, чтобы узнать почему.


Для вашего кода у вас есть варианты:

  • Использовать конструктор BigDecimal(int) для всей суммы, например, 5, но не 0.50

  • Используйте конструктор BigDecimal(String) для любого количества, например, "5" и "0.50"

  • Использование BigDecimal.valueOf(double) stati c метод, если вам нужно, чтобы значение было double.

...