1

I have a function that rounds a float to n number of digits using BigDecimal.setScale

private float roundPrice(float price, int numDigits) {
    BigDecimal bd = BigDecimal.valueOf(price);
    bd = bd.setScale(numDigits, RoundingMode.HALF_UP);
    float roundedFloat = bd.floatValue();
    return roundedFloat;
}

public void testRoundPrice() {
    float numberToRound = 0.2658f;
    System.out.println(numberToRound);
    float roundedNumber = roundPrice(numberToRound, 5);
    System.out.println(roundedNumber);
    BigDecimal bd = BigDecimal.valueOf(roundedNumber);
    System.out.println(bd);
}

Output:

0.2658
0.2658
0.26579999923706055

How can I prevent BigDecimal from adding all these extra digits at the end of my rounded value?

NOTE: I can't do the following, because I dont have access to the number of digits in the api call function.

System.out.println(bd.setScale(5, RoundingMode.CEILING));
jeninja
  • 788
  • 1
  • 13
  • 27
  • See this: https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html#round(java.math.MathContext) – kiner_shah Nov 12 '21 at 04:18
  • 7
    Using float at *any point* destroys the benefits of BigDecimal. To have a consistent number of decimal places, you can't have any floats anywhere. – Louis Wasserman Nov 12 '21 at 04:21
  • @LouisWasserman so if I use a double instead, that should fix it? – jeninja Nov 12 '21 at 04:23
  • 4
    @jeninja A double is a floating-point number, just with a few extra digits. _Use `BigDecimal`_. – chrylis -cautiouslyoptimistic- Nov 12 '21 at 04:25
  • If you want to preserve the exact number `0.2658` then you can do some nasty conversion from Float to String to BigDecimal like so `BigDecimal converted = new BigDecimal(Float.toString(numberToRound));` – sorifiend Nov 12 '21 at 04:35
  • 2
    @jeninja: No. You can't use anything but BigDecimal. Any float, any double, can ruin the decimal precision. Don't even use it to initialize the BigDecimal; use a String, e.g. `new BigDecimal("0.2658")` – Louis Wasserman Nov 12 '21 at 04:56
  • It's not `BigDecimal` that's adding the digits. The problem is that there is no `float` that's exactly 0.2658. So when you write `0.2658f`, you actually get a float whose value is `0.265799999237060546875`. If you want something that's exactly 0.2658, you can't use `float`, or `double`. In that case, `BigDecimal` is the best choice. – Dawood ibn Kareem Nov 12 '21 at 06:17

2 Answers2

5

It’s the other way around. BigDecimal is telling you the truth. 0.26579999923706055 is closer to the value that your float has got all the time, both before and after rounding. A float being a binary rather than a decimal number cannot hold 0.2658 precisely. Actually 0.265799999237060546875 is as close as we can get.

When you print the float, you don’t get the full value. Some rounding occurs, so in spite of the float having the aforementioned value, you only see 0.2658.

When you create a BigDecimal from the float, you are really first converting to a double (because this is what BigDecimal.valueOf() accepts). The double has the same value as the float, but would print as 0.26579999923706055, which is also the value that your BigDecimal gets.

If you want a BigDecimal having the printed value of the float rather than the exact value in it or something close, the following may work:

    BigDecimal bd = new BigDecimal(String.valueOf(roundedNumber));
    System.out.println(bd);

Output:

0.2658

You may get surprises with other values, though, since a float hasn’t got that great of a precision.

EDIT: you were effectively converting float -> double -> String -> BigDecimal.

These insightful comments by Dawood ibn Kareem got me researching a bit:

Actually 0.265799999237060546875.

Well, 0.26579999923706055 is the value returned by calling toString on the double value. That's not the same as the number actually represented by that double. That's why BigDecimal.valueOf(double) doesn't in general return the same value as new BigDecimal(double). It's really important to understand the difference if you're going to be working with floating point values and with BigDecimal.

So what really happened:

  1. Your float internally had the value of 0.265799999237060546875 both before and after rounding.
  2. When you are passing your float to BigDecimal.valueOf(double), you are effectively converting float -> double -> String -> BigDecimal.
    • The double has the same value as the float, 0.265799999237060546875.
    • The conversion to String rounds a little bit to "0.26579999923706055".
    • So your BigDecimal gets the value of 0.26579999923706055, the value you saw and asked about.

From the documentation of BigDecimal.valueOf(double):

Translates a double into a BigDecimal, using the double's canonical string representation provided by the Double.toString(double) method.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 1
    Well, `0.26579999923706055` is the value returned by calling `toString` on the `double` value. That's not the same as the number actually represented by that `double`. That's why `BigDecimal.valueOf(double)` doesn't in general return the same value as `new BigDecimal(double)`. It's really important to understand the difference if you're going to be working with floating point values and with `BigDecimal`. – Dawood ibn Kareem Nov 14 '21 at 07:39
  • @DawoodibnKareem Indeed you’re right! Today I learned. Thank you. I have edited. – Ole V.V. Nov 14 '21 at 08:14
  • I've upvoted. Your edit made the answer much better. – Dawood ibn Kareem Nov 14 '21 at 08:18
2

I've decided to modify my program to use BigDecimal as the base type for my property price in my object instead of type float. Although tricky at first it is definitely the cleaner solution in the long run.

public class Order {
    // float price; // old type
    BigDecimal price; // new type
}
jeninja
  • 788
  • 1
  • 13
  • 27