maks
maks

Reputation: 6006

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

Upvotes: 4

Views: 4909

Answers (1)

andy
andy

Reputation: 2993

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.

Upvotes: 4

Related Questions