Set BigDecimal precision only if decimals repeating - java

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.

Related

BigDecimal losing precision on divide

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
}

Why is BigDecimal returning an approximation of my large double in Java?

I'd like to round my large double so the first thing I decided to do, was to convert it into a BigDecimal in the following way.
BigDecimal amount = BigDecimal
.valueOf(getAmount())
.setScale(2, RoundingMode.HALF_UP);
System.out.println(amount);
In my example, getAmount() returns 123456789123123424113.31.
Therefore, I expect the exact same value to be printed out by my snippet.
Instead, I get the following value:
123456789123123430000.00
Can someone explain why BigDecimal is returning an approximation of my double?
In my example, getAmount() returns 123456789123123424113.31.
No, it does not. That is not a value that a double can represent exactly.
You can easily verify that with this code:
double d = 123456789123123424113.31d;
System.out.println(d);
Which outputs
1.2345678912312343E20
This value has the minimum amount of digits to uniquely distinguish it from any other double value. Meaning that there aren't any more relevant digits in that double. You've already lost the precision before converting the value to BigDecimal.
While an integer data type such as long and int can exactly represent every (integer) value within its range, the same can't be said about floating point numbers: they have an immense range of values that they can represent, but at the cost of not being able to represent every possible value within the range. Effectively there's a limited number of digits that a floating point number can represent (about 16 decimal digits for double and about 7 decimal digits for float). Everything else will be cut off.
If you need arbitrary precision then something like BigDecimal can help: it will allocate as much memory as necessary to hold all digits (or round according to your specification, if required), making it much more complex but also more powerful.
BigDecimal bd = new BigDecimal("123456789123123424113.31");
System.out.println(bd);
will print
123456789123123424113.31
Make sure not to initialize the BigDecimal from a double value, as you'll only get the cut-off value even then.

Calculations without loss of precision

I have
BigDecimal a = new BigDecimal(7);
BigDecimal b = new BigDecimal(13);
BigDecimal c = new BigDecimal(26);
System.out.print((a.divide(b)).multiply(c));
this code generates an exception:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
It means that I need to set RoundingMode.
But I need to get result only without loss of precision.
How can I achieve it?
Seeing as 7/13 goes on to infinity what you're asking for is not possible. Your only possible option is to have a large precision when you divide.
Dividing 7 by 13 will giving you long and non terminating decimal(0.53846153846.....). So you need to set the precision limit to it. Try this:
a.divide(b, x, RoundingMode.HALF_UP).multiply(c)
where x is the precision limit you want to set.
The Javadocs says:
When a MathContext object is supplied with a precision setting of 0
(for example, MathContext.UNLIMITED), arithmetic operations are exact,
as are the arithmetic methods which take no MathContext object. (This
is the only behavior that was supported in releases prior to 5.)
As a corollary of computing the exact result, the rounding mode
setting of a MathContext object with a precision setting of 0 is not
used and thus irrelevant. In the case of divide, the exact quotient
could have an infinitely long decimal expansion; for example, 1
divided by 3.
Decimal places of 7/3 is going to infinity. You must round it AFAIK. You can keep 6 decimal places because first 6 places repeats infinitely
http://m.wolframalpha.com/input/?i=7%2F13
Apache commons library can it
Fractions

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.

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.

Categories

Resources