BigDecimal losing precision on divide - java

Consider the following BigDecimals
BigDecimal("6.0000").precision() // = 5
BigDecimal("0.20000").precision() // = 5
When you divide those BigDecimals:
BigDecimal("6.0000").divide(BigDecimal("0.20000")) // = 3E+1
And
BigDecimal("6.0000").divide(BigDecimal("0.20000")).precision() // = 1
So dividing two BigDecimals with a precision of 5 results in BigDecimal with a precision of 1. Even when explicitly setting the precision to 5 by providing a MathContext, the result is the same:
BigDecimal("6.0000").divide(BigDecimal("0.20000"), MathContext(5, RoundingMode.HALF_UP)) // = 3E+1
When I set the scale on the other hand, I end up with a higher precision
BigDecimal("6.0000").divide(BigDecimal("0.20000"), 5, RoundingMode.HALF_UP).precision() // = 7
Is there a way to keep the precision when performing a division as above? Is this only possible by specifying a scale instead of a precision?
Regarding the scale, the javadoc states that the preferred scale for divisions is dividend.scale() - divisor.scale(). However, it also states that
These scales are the ones used by the methods which return exact arithmetic results; except that an exact divide may have to use a larger scale since the exact result may have more digits. For example, 1/32 is 0.03125.
Isn't that the case in the above situation, as the exact results requires more digits?

In your case, it does not really matter, because the result is precise. If you need concrete precision (maybe for parsing), use setScale on the end.
If a function would need to lose precision to give you a result (for instance, because of dividing 1 by 3), dividing would throw an exception (in case of using the divide function, because Kotlin div operator would round the result, losing precision). That is why it is better to set precision and rounding mode by yourself when you divide.
import java.math.BigDecimal
import java.math.RoundingMode.HALF_EVEN
fun main() {
val a = BigDecimal("1.0")
val b = BigDecimal("3.0")
val c = a.divide(b, 10, HALF_EVEN)
println(c) // 0.3333333333
println(c.precision()) // 10
}

Related

Set BigDecimal precision only if decimals repeating

I know that I can use BigDecimal.divide() method to set a fixed precision:
BigDecimal a1 = new BigDecimal(1);
BigDecimal a2 = new BigDecimal(3);
BigDecimal division = a1.divide(a2,5,1); //equals 0.33333
But if the division result is exact:
BigDecimal a1 = new BigDecimal(1);
BigDecimal a2 = new BigDecimal(4);
BigDecimal division = a1.divide(a2,5,1); //equals 0.25000
How can I set the division result so if the division ends with an exact result, it will just give the output of 0.25 instead of 0.25000?
I've also tried to not specifying a precision by doing:
BigDecimal division = a1.divide(a2);
it succeeds in giving the result 0.25 when doing 1/4, but when it does division like 1/3 or 2/3 it results in a runtime ArithmeticException.
a1 and a2 are instantiated from user input.
Is there any way to solve this?
You get an ArithmeticException as described in BigDecimal.divide(BigDecimal) - "if the exact quotient does not have a terminating decimal expansion" because to properly represent the result of 1/3 would take infinite memory. By using BigDecimal.divide(BigDecimal,int,RoundingMode) you explicitly set what precision you'll tolerate, meaning any division can be approximated in-memory.
For consistency this version of division always sets the scale to what you specify, even if the value can be accurately represented with less precision. If it didn't, it would be difficult for you to reason about how precise any future computations using that result will be.
To reduce your precision use .stripTrailingZeros(); this effectively reduces the scale to the minimum possible. Do this as late in the process as possible (i.e. right before you print) to maintain that consistency I mentioned above.
You may also want .toPlainString() if you're trying to display these values nicely, since .stripTrailingZeros() on its own will display 100 as 1E+2.

BigDecimal in scala

I've stumbled across interisting thing(maybe only for me) in scala. In a word, if we have a BigDecimal(let say val a = BigDecimal(someValue) where someValue is decimal string) the result of operation
N * a / N == a
will not always produce true. I suppose that it relates to any opeartions on BigDecimals. I know that in scala BigDecimals are created with default MathContext set to DECIMAL128(with HALF_EVEN rounding and precision equals to 34). I've discovered such behavior on decimals with more than 30 digits after point
My questions is why I get such results. Can I somehow control them?
example
-0.007633587786259541984732824427480916
As previous comments already point out, this is not avoidable with irrational numbers. This is because there's no way to represent an irrational number using the standard numeric types (if at all). Since I have no examples with irrational numbers (even PI is limited to a fixed number of digits, and therefore can be expressed as a quotient of 2 whole numbers, making it rational), I will use repeating decimals to illustrate the problem. I changed N*a/N to a/N*N because it demonstrates the problem better with whole numbers, but they're equivalent:
a = BigDecimal(1)
N = BigDecimal(3)
a/N = 0.333...
a/N*N = 0.999...
As you can see in the example above, you can use as many decimal places and any rounding mode, but the result is never going to be equal to 1. (Though it IS possible to get 1 using a different rounding mode per operation, i.e. BigDecimal(3, roundHalfEven) * (BigDecimal(1, roundUp) / 3))
One thing you can do to control the number comparison is to use a higher precision when performing your arithmetic operations and round to the desired (lower) precision when comparing:
val HighPrecision = new java.math.MathContext(36, java.math.RoundingMode.HALF_EVEN);
val TargetPrecision = java.math.MathContext.DECIMAL128;
val a = BigDecimal(1, HighPrecision)
val N = BigDecimal(3, HighPrecision)
(a/N*N).round(TargetPrecision) == a.round(TargetPrecision)
In the example above, the last expression evaluates to true.
UPDATE
To answer your comment, although BigDecimal is arbitrary precision, it is still limited by a precision. It can be 34 or it can be 1000000 (if you have enough memory). BigDecimal does NOT know that 1 / 3 is 0.33<repeating>. If you think about how division works, there's no way for BigDecimal to conclusively know that it's repeating without performing the division to infinite decimal places. But since a precision of 2 indicates it can stop dividing after 2 decimal places, it only knows that 1 / 3 is 0.33.

Java's '==' operator on doubles

This method returns 'true'. Why ?
public static boolean f() {
double val = Double.MAX_VALUE/10;
double save = val;
for (int i = 1; i < 1000; i++) {
val -= i;
}
return (val == save);
}
You're subtracting quite a small value (less than 1000) from a huge value. The small value is so much smaller than the large value that the closest representable value to the theoretical result is still the original value.
Basically it's a result of the way floating point numbers work.
Imagine we had some decimal floating point type (just for simplicity) which only stored 5 significant digits in the mantissa, and an exponent in the range 0 to 1000.
Your example is like writing 10999 - 1000... think about what the result of that would be, when rounded to 5 significant digits. Yes, the exact result is 99999.....9000 (with 999 digits) but if you can only represent values with 5 significant digits, the closest result is 10999 again.
When you set val to Double.MAX_VALUE/10, it is set to a value approximately equal to 1.7976931348623158 * 10^307. substracting values like 1000 from that would required a precision on the double representation that is not possible, so it basically leaves val unchanged.
Depending on your needs, you may use BigDecimal instead of double.
Double.MAX_VALUE is so big that the JVM does not tell the difference between it and Double.MAX_VALUE-1000
if you subtract a number fewer than "1.9958403095347198E292" from Double.MAV_VALUE the result is still Double.MAX_VALUE.
System.out.println(
new BigDecimal(Double.MAX_VALUE).equals( new BigDecimal(
Double.MAX_VALUE - 2.E291) )
);
System.out.println(
new BigDecimal(Double.MAX_VALUE).equals( new BigDecimal(
Double.MAX_VALUE - 2.E292) )
);
Ouptup:
true
false
A double does not have enough precision to perform the calculation you are attempting. So the result is the same as the initial value.
It is nothing to do with the == operator.
val is a big number and when subtracting 1 (or even 1000) from it, the result cannot be expressed properly as a double value. The representation of this number x and x-1 is the same, because double only has a limited number of bits to represent an unlimited number of numbers.
Double.MAX_VALUE is a huge number compared to 1 or 1000. Double.MAX_VALUE-1 is generally equals to Double.MAX_VALUE. So your code roughly does nothing when substracting 1 or 1000 to Double.MAX_VALUE/10.
Always remember that:
doubles or floats are just approximations of real numbers, they are just rationals not equally distributed among the reals
you should use very carefully arithmetic operators between doubles or floats which are not close (there is many other rules such like this...)
in general, never use doubles or float if you need arbitrary precision
Because double is a floating point numeric type, which is a way of approximating numeric values. Floating point representations encode numbers so that we can store numbers much larger or smaller than we normally could. However, not all numbers can be represented in the given space, so multiple numbers get rounded to the same floating point value.
As a simplified example, we might want to be able to store values ranging from -1000 to 1000 in some small amount of space where we would normally only be able to store -10 to 10. So we could round all values to the nearest thousand and store them in the small space: -1000 gets encoded as -10, -900 gets encoded as -9, 1000 gets encoded as 10. But what if we want to store -999? The closest value we can encoded is -1000, so we have to encode -999 as the same value as -1000: -10.
In reality, floating point schemes are much more complicated than the example above, but the concept is similar. Floating point representations of numbers can only represent some of all the possible numbers, so when we have a number that can't be represented as part of the scheme, we have to round it to the closest representable value.
In your code, all values within 1000 of Double.MAX_VALUE / 10 automatically get rounded to Double.MAX_VALUE / 10, which is why the computer thinks (Double.MAX_VALUE / 10) - 1000 == Double.MAX_VALUE / 10.
The result of a floating point calculation is the closest representable value to the exact answer. This program:
public class Test {
public static void main(String[] args) throws Exception {
double val = Double.MAX_VALUE/10;
System.out.println(val);
System.out.println(Math.nextAfter(val, 0));
}
}
prints:
1.7976931348623158E307
1.7976931348623155E307
The first of these numbers is your original val. The second is the largest double that is less than it.
When you subtract 1000 from 1.7976931348623158E307, the exact answer is between those two numbers, but very, very much closer to 1.7976931348623158E307 than to 1.7976931348623155E307, so the result will be rounded to 1.7976931348623155E307, leaving val unchanged.

How do you trim the BigDecimal division results

BigDecimal numerator = new BigDecimal(numerator);
BigDecimal denominator = new BigDecimal(denominator);
double result = numerator.divide(denominator).doubleValue();
Division on certain conditions results in a zero at the end (e.g. 0.0060).
Dividing equal numbers results in a zero at the end (e.g 1.0).
I would prefer to trim the trailing zero in both cases. How should I do this?
How about keeping the result as a BigDecimal and then you can set the scale on it to only represent the significant figures that you want.
An easy way to do this, for some numbers, is to use BigDecimal#stripTrailingZeros(). However, if the number is an integer with trailing zeros you'll get an engineering representation e.g. 600.0 will give you 6E+2. If this isn't what you want, you'll have to detect this condition and manually use BigDecimal#setScale() to set the scale appropriately.
If you need to keep to a restricted maximum number of decimal digits you'll need to use alternative formatting/rounding mechanisms before applying this technique.
It's also a good idea to only do this on values that you're going to display, not on the internal values of your model. Treat it as a view/presentation layer modification.
If you must convert to a double, then it's only the formatted representation you can alter. In this case, if you've got a variable number of decimal places that you want to format to, I'd just drop it into a string/character array, scan backwards for the first non-zero character and truncate it there. Not the most performant means, but simple and reliable.
You could even use a regex for this purpose.
you can do this by using DecimalFormat. something like:
DecimalFormat df = new DecimalFormat(".0");
double formatResult = df.format(result);
will create something of 1.0 if the result is 1.278494890. there are many possible patterns that could be used here
Ok, so you've got this code:
BigDecimal numerator = new BigDecimal(numerator);
BigDecimal denominator = new BigDecimal(denominator);
double result = numerator.divide(denominator).doubleValue();
and you want more control over your output. Since result is a double, which is a primitive, you won't have much control.
From my understanding, you don't want to do any rounding to n decimal places, you want original precision paired with desired formatting.
You have few options.
BigDecimal numerator = new BigDecimal(numerator);
BigDecimal denominator = new BigDecimal(denominator);
BigDecimal div = numerator.divide(denominator);
If you stay with BigDecimal, your output will be better. If you put 10 as the numerator and denominator in above code, System.out.println(div) will yield 1.
Generally, be careful of using above code because some combinations of numerator and denominator will throw
java.lang.ArithmeticException: "Non-terminating decimal expansion; no exact representable decimal result."
If you want to avoid such situations, and not worry about precision beyond double's internal representation, use double directly.
System.out.println(2312 / 2.543); //909.1624066063704
System.out.println(1.0 / 1.0); //1.0
System.out.println(1 / 1); //1
When using double numbers, you might get a 0 at the end, such as 0.0060 in your case. If you want to be sure what you're getting, you'll have to convert your result to a String using
String dec = String.valueOf(10.0/10.0); //1.0
and then using
String newDec = dec.endsWith("0") ? dec.substring(0, dec.length() - 1) : dec;
to eradicate that last 0. Of course, if your string ends with .0, you have a choice based on your preferences whether you want to leave that leading . or not.

BigDecimal setScale and round

What is the difference between this two call? (Is there any?)
// 1.
new BigDecimal("3.53456").round(new MathContext(4, RoundingMode.HALF_UP));
// 2.
new BigDecimal("3.53456").setScale(4, RoundingMode.HALF_UP);
One important point that is alluded to but not directly addressed is the difference between "precision" and "scale" and how they are used in the two statements. "precision" is the total number of significant digits in a number. "scale" is the number of digits to the right of the decimal point.
The MathContext constructor only accepts precision and RoundingMode as arguments, and therefore scale is never specified in the first statement.
setScale() obviously accepts scale as an argument, as well as RoundingMode, however precision is never specified in the second statement.
If you move the decimal point one place to the right, the difference will become clear:
// 1.
new BigDecimal("35.3456").round(new MathContext(4, RoundingMode.HALF_UP));
//result = 35.35
// 2.
new BigDecimal("35.3456").setScale(4, RoundingMode.HALF_UP);
// result = 35.3456
There is indeed a big difference, which you should keep in mind. setScale really set the scale of your number whereas round does round your number to the specified digits BUT it "starts from the leftmost digit of exact result" as mentioned within the jdk. So regarding your sample the results are the same, but try 0.0034 instead.
Here's my note about that on my blog:
http://araklefeistel.blogspot.com/2011/06/javamathbigdecimal-difference-between.html

Categories

Resources