Class BigDecimal
has some useful methods to guarantee lossless conversion:
byteValueExact()
shortValueExact()
intValueExact()
longValueExact()
However, methods floatValueExact()
and doubleValueExact()
do not exist.
I read the OpenJDK source code for methods floatValue()
and doubleValue()
. Both appear to fallback to Float.parseFloat()
and Double.parseDouble()
, respectively, which may return positive or negative infinity. For example, parsing a string of 10,000 9s will return positive infinity. As I understand, BigDecimal
does not have an internal concept of infinity. Further, parsing a string of 100 9s as double
gives 1.0E100
, which is not infinity, but loses precision.
What is a reasonable implementation floatValueExact()
and doubleValueExact()
?
I thought about a double
solution by combining BigDecimal.doubleValue()
, BigDecial.toString()
, Double.parseDouble(String)
and Double.toString(double)
, but it looks messy. I want to ask here because there may (must!) be a simpler solution.
To be clear, I don't need a high performance solution.
From reading the docs, all it does with the numTypeValueExact
variants is to check for existence of a fraction part or if the value is too big for the numeric type and throw exceptions.
As for floatValue()
and doubleValue()
, a similar overflow check is being done, but instead of throwing an exception, instead it returns Double.POSITIVE_INFINITY
or Double.NEGATIVE_INFINITY
for doubles and Float.POSITIVE_INFINITY
or Float.NEGATIVE_INFINITY
for floats.
Therefore the most reasonable (and simplest) implementation of the exact
methods for float and double, should simply check if the conversion returns POSITIVE_INFINITY
or NEGATIVE_INFINITY
.
Furthermore, remember that BigDecimal
was designed to handle the lack of precision that comes from using float
or double
for large irrationals, therefore as @JB Nizet commented, another check you can add to the above would be to convert the double
or float
back to BigDecimal
to see if you still get the same value. This should prove the conversion was correct.
Here is what such a method would look like for floatValueExact()
:
public static float floatValueExact(BigDecimal decimal) {
float result = decimal.floatValue();
if (!(Float.isNaN(result) || Float.isInfinite(result))) {
if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
return result;
}
}
throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}
The use of compareTo
instead of equals
above is intentional so as to not become too strict with the checks. equals
will only evaluate to true when the two BigDecimal
objects have the same value and scale (size of the fraction part of the decimal), whereas compareTo
will overlook this difference when it does not matter. For example 2.0
vs 2.00
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With