1

Using java 11 and Intellij Idea.

I'm in the situation where I get a string of data from an external system and need to reformat this. In the current setup I have this line to extract the value for a sales price:

BigDecimal sellingPrice = BigDecimal.valueOf(Double.parseDouble(somesalesprice));

According to the debugger, the output value of sellingPrice = "4.350000000" Now when I convert this according to this line of code:

long roundedSellingPrice = (long) (sellingPrice.doubleValue() * 100);

the result turns out to be "434". As can be seen, 1 (cent) has magically disappeared. I double-checked the values and this consistent. Out of the 500+ dataset that I'm working with, approx. 40 other records seem to have this problem. For the majority it seems to be going just fine.

I cannot find anything common between these records.

Also, it turns out, the result is always rounded down by 1 cent, never up.

Wonder what this could be?

JustLudo
  • 1,690
  • 12
  • 29
  • 3
    Why are you doing computation in double when you have BigDecimal value? That makes no sense. – wilx Dec 29 '22 at 12:59
  • 2
    You are losing precision in parsing, again in conversion to `BigDecimal` and aagin in casting to `long`. Of course you are not getting a precise result. Use `new BigDecimal(somesalesprice)`. – Ole V.V. Dec 29 '22 at 13:01
  • 1
    In fact, you should be using the String parameter constructor of BigDecimal to parse the number. – wilx Dec 29 '22 at 13:01
  • 2
    `new BigDecimal("4.350000000").multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.DOWN)` gives `435`. Take a stand on which rounding mode you want, there are some to choose from. – Ole V.V. Dec 29 '22 at 13:10
  • 1
    This is a consequence of rounding versus truncation. 4.35 held as a `double` actually holds `4.3499999999999996447286321199499070644378662109375` and while `BigDecimal.valueOf()` has rounded the value the conversion to `long` has truncated. – Persixty Dec 29 '22 at 13:11
  • Avoid using `double` or `float` for monetary amounts where possible. They are not exact decimal values and will from time to time due to rounding or error accumulation let you down. – Persixty Dec 29 '22 at 13:14
  • 1
    Thanks for the comments. Using jshell I found some weird precision with doublevalue on some values ("2.3", "4.35", etc.). I did not write the original code myself, but have refactored it to use BigDecimal only according to @Michael Gantman's answer and that solved it. – JustLudo Dec 29 '22 at 14:57
  • 1
    @JustLudo This may be useful. It helps convert decimal to the closest float-point values and you'll see again. 2.3 isn't stored precisely. https://baseconvert.com/ieee-754-floating-point – Persixty Dec 29 '22 at 15:40
  • 1
    Related and informative: [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Ole V.V. Dec 31 '22 at 12:15

1 Answers1

2

As weird as it sounds for the data of types Float and Double the precision is not very good. Use BigDecimal for all the calculations. That's the short answer.

In Addition, when you parse a String to any numerical type you have to deal with exceptions. Sometimes it makes sense and sometimes it doesn't. For the case when you don't care about exception I wrote a utility that parses string to any numeric value and if parsing fails it returns a default value. In case you want your exception messages to be printed into log the same utility optionally may print exception messages into a log. See methods:
public static java.math.BigDecimalparseStringToBigDecimal(java.lang.CharSequence num, java.math.BigDecimal defaultValue)
and
public static java.math.BigDecimal parseStringToBigDecimal(java.lang.CharSequence num, java.math.BigDecimal defaultValue, java.lang.String nullOrEmptyStringErrorMessage, java.lang.String numberFormatErrorMessage)
Those methods can be pretty useful. The utility is comes with Open Source MgntUtils library written and maintained by me. You can get it as maven artifacts or at Github

Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
  • 1
    Never use `float` or `double` to hold amounts of currency. – Persixty Dec 29 '22 at 13:17
  • Thanks for the answer. After refactoring to BigDecimal indeed the problem ceased to exist. Doublevalue gave some weird results (as tested in jshell) with lack of precision, but only on some values. – JustLudo Dec 29 '22 at 14:55