Streams are a very powerful way to express a computation. It is natural to desire to use them for arithmetical computations and actually quite convenient but the float/double rounding implied in such a transformation can mess up your result.

Side note: in this post I'll speak of float -> double conversion but you can hit the same issue with double, it is just not a bit less natural to fall into this other trap.

To illustrate this issue we'll assume we have some Account with a balance and youwewant to sum up the balances of a user to get its global balance. This can look like:

public class Account {
    private float balance;

    public Account(final float balance) {
        this.balance = balance;
    }

    public float getBalance() {
        return balance;
    }
}

Indeed in real life you would get a name, account number etc but we don't need that for this post ;).

Assuming you loaded all the user accounts in an array (a collection would work too and covers JPA cases):

final Account[] values = new Account[]{
  new Account(1.f),
  new Account(1.1f)
};

Then you would do the sum using this kind of stream:

double sum = Stream.of(values)
  .mapToDouble(Account::getBalance)
  .sum();

This looks natural no? Actually not that much because we used mapToDouble even if our balances are floats. Why? Because stream doesn't provide a mapToFloat.

Now if you check that sum you would get:

2.100000023841858

Hmm, so 1 + 1.1 > 2.1....not sure our math teacher would enjoy that result.

Then you want to check it and you do the sum manually for this case:

values[0].getBalance() + values[1].getBalance()

And here you get 2.1...what's wrong?

The double conversion is the one responsible of this error, you can verify it with these 2 simple lines:

System.out.println(1.1f + 1.d);
System.out.println(1.1f + 1.f);

Output is:

2.100000023841858
2.1

If there is a conversion of a float to a double, the float if "corrupted" and therefore the result of the operation (here we use a sum but it can be any of the primitive operations).

Even weirder is if you cast the float in Number to call doubleValue(), you will still get this error...Float wrapper doesn't handle the conversion properly even if sun.misc has all it needs to do it :(.

This kind of conversion to a wider type is called "widening primitive conversion".

Side note: the opposite exists and is naturally named "narrowing" instead of "widening".

How to solve it? There are mainly 3 options:

  • convert floats to string then get a double
float myfloat = 1.1f;
double value = new BigDecimal(Number.class.cast(myfloat).toString()).doubleValue();

// alternative
double value = Double.parseDouble(Number.class.cast(myfloat).toString());

The trick here is to use a BigDecimal and not just the number doubleValue() since the float would be converted in Float as Number and then would just use a cast which was proven being wrong.

  • use only floats or only doubles (means if you want to use streams you will use only doubles)
  • use some binary mask magic to force the right part of the double "structure" to be 0 if the value has a decimal part (take care structure is not linear as for int/long, it uses the concept of mantissa)
  • don't use floats/doubles

More generally if you do some serious computation, using BigDecimal is saner but as streams (and like streams) it doesn't have a float flavor/constructor so you still need to use the string constructor to not loose any precision.

In any case, I hope this post convinced you to test with not too trivial values any computation you do and make you think about your flow instead of blindly following stream fluent API ;).

From the same author:

In the same category: