21
float ff = (float)31.15;

double dd = 31.15;

var frst = Math.Round(ff, 1, MidpointRounding.AwayFromZero);

var drst = Math.Round(dd, 1, MidpointRounding.AwayFromZero);

frst: 31.1

drst: 31.2

Can someone explain why?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Yaoqing
  • 283
  • 2
  • 3
  • 3
    Because `(float)31.15` is not equal to `(double)31.15`. Floating-point-arithmetic allmost allways yields to rounding-erros. In paticular rounding a double works different from rounding a float. – MakePeaceGreatAgain Jan 17 '19 at 12:26
  • Don't use `float`. Always `decimal` or `double`. – i486 Jan 17 '19 at 12:31
  • 1
    Rounding errors are unavoidable with floating point values. They are as unavoidable as death and taxes : https://www.youtube.com/watch?v=PZRI1IfStY0 – Christopher Jan 17 '19 at 12:31
  • 4
    @i486 Well, not always, there is a reason why `float` exists. – SᴇM Jan 17 '19 at 12:33
  • @SeM `float` exists in C# mostly for compatibility with C. And in C it is important in embedded systems or was important in old PCs when RAM size was limited. Imagine that you have 256KB (not MB) and need array of real numbers - every `float` value needs 4 bytes and `double` needs 8 bytes. – i486 Jan 17 '19 at 13:26
  • 4
    @i486 Again, that doesn't mean, that you should never use `float`. – SᴇM Jan 17 '19 at 13:29
  • @SeM Nearly "never" in C#. Or at least for every low experienced programmer this is simple rule. – i486 Jan 17 '19 at 13:31
  • 1
    @HimBromBeere `(float)31.15` is a **double** value converted to float, which results in [double rounding error](https://en.wikipedia.org/wiki/Rounding#Double_rounding) and may not be the same as `31.15f` (which is the closest value to 31.15 in float precision) [Why do you need to specify an 'f' in a float literal?](https://stackoverflow.com/q/14102955/995714), [Why comparing double and float leads to unexpected result?](https://stackoverflow.com/q/6722293/995714), [strange output in comparison of float with float literal](https://stackoverflow.com/q/1839422/995714) – phuclv Jan 17 '19 at 15:17
  • 1
    another duplicate: [Is floating point math broken?](https://stackoverflow.com/q/588004/995714) – phuclv Jan 17 '19 at 15:25
  • @i486 float is still useful in many cases, especially in arrays. It halves the amount of bandwidth needed which is the bottleneck in many applications where precision is not really important, that's why in imaging or AI they even use 16-bit floats – phuclv Jan 17 '19 at 15:28
  • @phuclv And this is in C#? Or C. If `double` array is bottleneck then CLR/C# is also bottleneck. I mentioned above that recommendation for `double` is for beginners - avoid `float` for the first time (years of programming). – i486 Jan 17 '19 at 15:48
  • 1
    @i486 it's not just memory (double taking twice the space), it's also speed, vectorisation for 3D graphics (four floats fit into a 128 bit vector), etc. It's important to choose the right data types for your needs, not blanket dismiss entire data types. – BittermanAndy Jan 17 '19 at 17:22
  • 4
    Where are the C# gold badges? IMHO this should have been closed as dup in the first 17s, not 5 _hours_ after! – YSC Jan 17 '19 at 17:47

2 Answers2

30

Well, Math.Round wants double, not float, that's why

Math.Round(ff, 1, MidpointRounding.AwayFromZero);

equals to

Math.Round((double)ff, 1, MidpointRounding.AwayFromZero);

and if we inspect (double)ff value

Console.Write(((double)ff).ToString("R"));

we'll see round up errors in action

31.149999618530273

Finally, Math.Round(31.149999618530273, 1, MidpointRounding.AwayFromZero) == 31.1 as expected

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
15

In floating point, all numbers are represented internally as fractions where the denominator is a power of 2.

(This is a similar way to how decimals are actually fractions with power-of-10 denominators. So 31.15 is just a way of writing the fraction 3115/100)

In floating point, 31.15 must be represented internally as a binary number. The closest binary fraction is: 1111.1001001100110011001100110011001100110011001100110011001100...repeating

The 1100 recurs (repeats forever), and so the number will be truncated depending on whether it is stored in a double or a float. In a float it is truncated to 24 digits, and in a double to 53.

Exact:  1111.100100110011001100110011001100110011001100110011001100110011001100...forever
Float:  1111.10010011001100110011
Double: 1111.1001001100110011001100110011001100110011001100110

Therefore you can see that the double that this number converts to, is actually slightly larger than the float it converts to. So it is clear that it won't necessarily round to the same number, since it is not the same number to begin with.

Ben
  • 34,935
  • 6
  • 74
  • 113