Reputation: 33
I am making a change return program and this seemingly unsolvable problem is really frustrating.
I'm using BigDecimal so I can work with precise results but two of my nine BigDecimal.remainder() method is returning irrational and inaccurate values (the other seven works fine), which is making my end result inaccurate.
For example, if the input is 70.70 and 100, the output will be one extra 5 cents coin.
I have highlighted the problem in my code. Any help is very appreciated. Thank you in advance.
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);
}
}
Upvotes: 3
Views: 1024
Reputation: 159155
Don't ever use the BigDecimal(double)
constructor. As the javadoc says:
The results of this constructor can be somewhat unpredictable.
Read the javadoc to learn why.
For your code, you have options:
Use the BigDecimal(int)
constructor for a whole amount, e.g. 5
but not 0.50
Use the BigDecimal(String)
constructor for any amount, e.g. "5"
and "0.50"
Use the BigDecimal.valueOf(double)
static method if you need input to be a double
value.
Upvotes: 1
Reputation: 97302
Even though you're working with BigDecimal
objects to obtain precise results, you're introducing an imprecision when declaring the objects that represent the bills and coins:
BigDecimal fifty = new BigDecimal(50.0);
BigDecimal twenty = new BigDecimal(20.0);
// ...
The values passed to the constructor are interpreted as doubles, but some of them cannot be accurately captured in a 64 bit double-precision format.
You should use the String
-based constructor instead:
BigDecimal fifty = new BigDecimal("50.0");
BigDecimal twenty = new BigDecimal("20.0");
// ...
This will give you the correct output:
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
Upvotes: 3