Unexpected BigDecimal result. 1 divided by 3 results in 1. Unsure why
The result of num.divide
is ignored. You need to set that back into a variable
BigDecimal num = new BigDecimal(1.00).setScale(2);
BigDecimal divisor = new BigDecimal(3.00).setScale(2);
BigDecimal result = num.divide(divisor, BigDecimal.ROUND_HALF_UP);
System.out.print(result);
This prints 0.33
Weird rounding issue
This turned out to be a regression in rails. It's reproducible with 5.0.0.1??? and is gone by 5.1.0.0???.
I bisected it and found this commit to be the one that fixes the issue. This is the related issue.
The fix seems to be to stop using the pg gem's float encoder.
BigDecimal gives unexpected results for numbers larger than 7 or 0 decimal numbers
As per the documentation, divide(BigDecimal divisor, RoundingMode roundingMode)
returns a BigDecimal
whose value is (this / divisor), and whose scale is this.scale()
.
Why did you get the expected result for 21099000.1 / 13196000.1?
Check the result of the following code:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.1);
BigDecimal second = BigDecimal.valueOf(13196000.1);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
Output:
First: 21099000.1, Scale: 1
Second: 13196000.1, Scale: 1
1.6
As you can see, JVM has chosen the scale as 1
for first
and thus the result of divide
(which is 1.5988936041
) is also set as 1
which is equal to 1.6
with RoundingMode.HALF_UP
.
Why did you not get the expected result for 21099000.0 / 13196000.0?
Check the result of the following code:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
// 21099000.0 / 13196000.0 = 1.5988936041
System.out.println(BigDecimal.valueOf(1.5988936041).setScale(first.scale(), RoundingMode.HALF_UP));
}
}
Output:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
0E+3
As you can see, JVM has chosen the scale as -3
for first
and thus the result of divide
(which is 1.5988936041
) is also set as -3
which is equal to 0
(or 0E+3
) with RoundingMode.HALF_UP
.
How can I change this behavior?
As mentioned in the documentation, scale of the division is set as this.scale()
which means if you set the scale of first
to 1
, you can get the expected result.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0).setScale(1);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, RoundingMode.HALF_UP).doubleValue());
}
}
Output:
First: 21099000.0, Scale: 1
Second: 1.3196E+7, Scale: -3
Division: 1.6
What is the most common way?
The last example worked well and there is no problem using it. However, the most common way is to use divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal first = BigDecimal.valueOf(21099000.0);
BigDecimal second = BigDecimal.valueOf(13196000.0);
System.out.println("First: " + first + ", Scale: " + first.scale());
System.out.println("Second: " + second + ", Scale: " + second.scale());
System.out.println("Division: " + first.divide(second, 1, RoundingMode.HALF_UP).doubleValue());
}
}
Output:
First: 2.1099E+7, Scale: -3
Second: 1.3196E+7, Scale: -3
Division: 1.6
Problem with Java BigDecimal.remainder() - irrational and inaccurate returns
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
Stange values after BigDecimal initialization
Use BigDecimal.valueOf(double)
static method: this will initialize the BigDecimal
from string representation of the double number which is what you need.
Deprecated constructor for BigDecimal.setScale(int, int) and RoundingMode enums
According to the docs, setScale(int, int)
, has not been recommended since Java 1.5, when enums were first introduced:
The new
setScale(int, RoundingMode)
method should be used in preference to this legacy method.
It was finally deprecated in Java 9.
You should call setScale(2, RoundingMode.HALF_EVEN)
instead. It makes error checking much simpler, since you can't pass in an undefined enum
, but you can certainly pass in an integer mode which is undefined.
BigDecimal - to use new or valueOf
Those are two separate questions: "What should I use for BigDecimal
?" and "What do I do in general?"
For BigDecimal
: this is a bit tricky, because they don't do the same thing. BigDecimal.valueOf(double)
will use the canonical String
representation of the double
value passed in to instantiate the BigDecimal
object. In other words: The value of the BigDecimal
object will be what you see when you do System.out.println(d)
.
If you use new BigDecimal(d)
however, then the BigDecimal
will try to represent the double
value as accurately as possible. This will usually result in a lot more digits being stored than you want. Strictly speaking, it's more correct than valueOf()
, but it's a lot less intuitive.
There's a nice explanation of this in the JavaDoc:
The results of this constructor can be somewhat unpredictable. One might assume that writing
new BigDecimal(0.1)
in Java creates aBigDecimal
which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as adouble
(or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
In general, if the result is the same (i.e. not in the case of BigDecimal
, but in most other cases), then valueOf()
should be preferred: it can do caching of common values (as seen on Integer.valueOf()
) and it can even change the caching behaviour without the caller having to be changed. new
will always instantiate a new value, even if not necessary (best example: new Boolean(true)
vs. Boolean.valueOf(true)
).
Android - Issue with conversion of double to Big Decimal
You're not using the result of round
. You should at least be doing:
totalPrice = round(totalPrice, 2, BigDecimal.ROUND_HALF_UP);
However, you shouldn't be using double
for currency values to start with. Use BigDecimal
throughout - otherwise you may well find you get unexpected results due to the way binary floating point works.
Related Topics
Class Method VS Constant in Ruby/Rails
Rspec: How to Stub an Instance Method Called by Constructor
Conditional Key/Value in a Ruby Hash
In Ruby What Does "=>" Mean and How Does It Work
Iconv Deprecation Warning with Ruby 1.9.3
Uninitialized Constant Rake::Dsl in Ruby Gem
Writing Over Previously Output Lines in the Command Prompt with Ruby
Repairing Postgresql After Upgrade to Osx Mavericks
Case-Insensitive Array#Include
Ruby: Sum Corresponding Members of Two or More Arrays
Bundle Can't Install Rmagick Gem on MAC Osx 10.7
One Liner in Ruby for Displaying a Prompt, Getting Input, and Assigning to a Variable