3

We have this input field where the user is supposed to enter a percentage. We display the percentage with 1 decimal. If the user choose to enter 31.45 we noticed that the float representation is 31.44999999999 which is correctly formatted as 31.4, but proper math rounding rules says the original numeric value should be rounded to 31.5.

So I looked for similar questions but most answers says that the string formatters handle should handle this case correctly. However it does not for me.

I tried the following C-code:

#include <math.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    float x = 31.45;

    printf("%.20f\n", x);
    printf("%.3f\n", x);
    printf("%.2f\n", x);
    printf("%.1f\n", x);
    printf("%.0f\n", x);

    double y = 31.45;

    printf("%.20f\n", y);
    printf("%.3f\n", y);
    printf("%.2f\n", y);
    printf("%.1f\n", y);
    printf("%.0f\n", y);

    return 0;
}

The output:

31.45000076293945312500
31.450
31.45
31.5
31
31.44999999999999928946
31.450
31.45
31.4
31

Note that float handles it correctly, while double doesn't!

I tried the same thing in Python:

>>> "%.1f" % 31.45
'31.4'

I tried with Wolfram Alpha: http://www.wolframalpha.com/input/?i=round+31.45+to+nearest+.1: same result.

NSNumberFormatter in Obj-C also has the same problem.

Excel handles it correctly though! 31.45 is formatted as 31.5 when using one decimal.

I'm not sure what to think of this. I was imagining that the string formatters would be smart enough to handle the limitations of floating point values, but it seems most do not. Any hints how to handle this in a generic way? Also, is there any logic to why floats behave better than double in this case, or is it just "luck"?

Jonatan
  • 3,752
  • 4
  • 36
  • 47
  • 2
    http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – too honest for this site Jun 07 '16 at 12:35
  • FWIW, if you want better precision and predictable rounding, use either integers and scale them or use a (third-party) class that already does this for you. – Rudy Velthuis Jun 07 '16 at 13:45
  • 4
    I disagree with you. Anything below exactly 31.45 should be rounded down to 31.4, so 31.449999 should be rounded down alright. Note that tie breaking rules (no matter which one) only apply to exactly half the next higher decimal, not to anything below or above. As others said: *this (31.44999...) is not a tie*, so tie-breaking rules do not apply. – Rudy Velthuis Jun 07 '16 at 13:49

4 Answers4

4

They don't, you're just reading your tests incorrectly, because of the difference between 31.45000076293945312500 and 31.44999999999999928946.

The main point is to ask for what you want by using floor / ceil / round and reading the docs to ensure you know what you're going to get.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • see also http://stackoverflow.com/questions/1343890/rounding-number-to-2-decimal-places-in-c – Wain Jun 07 '16 at 12:39
  • Not sure I follow you, but I do understand that the floats happen to be represented differently with float and double types, and that the rounding works as I expected with float due to that. With Wolfram Alpha I asked to round to the nearest 0.1, which was not what I expected, but I think Selcuks answer explains why. – Jonatan Jun 07 '16 at 12:42
4

This is a result of double rounding. First, 31.45 is rounded to the closest representable double, which is exactly 31.449999999999999289457264239899814128875732421875. Then, when you print, you round that to three decimal places, giving 31.4 (regardless of whether ties are rounded to even -- this is not a tie!).

If you want your floating-point values to behave as you expect with decimal arithmetic, use a decimal floating-point type.

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
0

You should understand Floating point number only gives you an approximation to the decimal number.

One common way to deal with the slight differences caused by the approximation is to decide what is the decimal places your application needs to support, and use that smallest unit as adjustment to arithmetic.

I wish you know you shouldn't do if (floatA == floatB)? Instead you need to do if (floatA > floatB - EPSILON && floatA < floatB + EPSILON) (where EPSILON is the smallest unit as described above)

Similarly, before you want a "precise" rounding, you should do something like printf("%.20f\n", y + EPSILON);

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
  • Thanks. This is what we do on platforms other than iOS, where NSNumberFormatter seems to do what we want automatically once `roundingMode` is set correctly. – Jonatan Jun 08 '16 at 06:14
-3

This has nothing to do with precision but the rounding algorithm used. Most languages use round half to even rounding instead of a biased rounding such as round half up. For example:

GNU Libc:

If the result is midway between two representable values, the even representable is chosen

.NET:

The Round method supports two rounding conventions for handling midpoint values:

Rounding away from zero

Rounding to nearest, or banker's rounding

You can read more about rounding methods on Wikipedia.

Community
  • 1
  • 1
Selcuk
  • 57,004
  • 12
  • 102
  • 110
  • Thank you! I was completely unaware of that. I thought round half up was used in all cases. – Jonatan Jun 07 '16 at 12:51
  • i meant "round away from zero" – Jonatan Jun 07 '16 at 13:05
  • 5
    But in this case, it's irrelevant which way ties are rounded, because the value given *isn't a tie*! For the `double` value, the correct result from both round half to even *and* from round half up would be `31.4`. And indeed, that's what both Python 2 and Python 3 give, even though Python 2's `round` uses round-ties-away-from-zero and Python 3's `round` uses round-ties-to-even. – Mark Dickinson Jun 07 '16 at 13:05
  • Why isn't it a tie? Because of the float rounding errors? I understand that, but as I noted in the original question, code that handles floats (formatters for example) should be aware of these limitations and handle them correctly anyway. You can't treat non-perfect numeric representations as if they were correct. – Jonatan Jun 07 '16 at 13:08
  • 2
    @Jonatan: Yes, exactly, because of the float rounding errors. With binary floating-point, What You See is Not What You Get! When you type the double literal `31.45` in your code, the value that's actually stored is (on a typical machine) `31.449999999999999289457264239899814128875732421875`. So that value is *already* smaller than the 31.45 halfway case, and the rounding algorithm correctly rounds it down to 31.4. – Mark Dickinson Jun 07 '16 at 13:11
  • @MarkDickinson please read my question and comment above again. – Jonatan Jun 07 '16 at 13:12
  • so, for example, if i was writing a formatter that should round a number to one decimal, i would look if the number was very close (for example the nearest floating point representation) to .5 and in that case treat it as .5. Since we know that floats have rounding errors we cannot expect them to be perfect, but rather try to do what's "expected" every time. And it seems like libc, python, java etc does just that, and what got me confused was that there was more than one way to round a number. – Jonatan Jun 07 '16 at 13:16
  • 4
    @Jonatan: That would be horribly imprecise and DWIM-ish. The whole of IEEE 754 is based on the idea that a float has a precise, exact, value, and all computations (including base conversion) should be performed on that exact value. That makes it clear, simple and predictable. Your proposal would make things much murkier. The key point is your use of the words "very close": there's no single choice for "very close" that's going to be suitable for all applications, and it would be a mistake to make one up. – Mark Dickinson Jun 07 '16 at 13:20
  • 3
    @Jonatan: What is "very close"? Is `0.3-0.2-0.1 = -2.77555756156289135105907917022705078125e-17` "very close" to zero? What about the reciprocal of Avogadro's number? What about Planck's constant? Which of these do you figure everyone should treat as zero, how can you write code to do that, and what implication would that have for chemistry, physics, or engineering? – tmyklebu Jun 07 '16 at 13:36
  • @tmyklebu Perhaps I've not been totally clear that I mean rounding when formatting a float as a string. As part of a calculation it is most likely a different story. With "very close" I mean something like the difference between a value and its closest float representation. I'm not sure, but I guess you could do that in the same way that when the C-compiler see "31.45" and decides to represent it as 31.44999... it does so because it is the closest representation available. Perhaps adding 1 to the significand and check if it lands above .5? – Jonatan Jun 07 '16 at 13:48
  • I think a lot of you guys have misread the question and my comments and have downvoted this answer by @Selcuk unfairly. The reason this question was brought up was because of NSNumberFormatter was behaving unexpectedly to us. After reading this answer we found the `roundingMode` property and when we selected another rounding mode it works just as we expected it to from the beginning. – Jonatan Jun 07 '16 at 13:58
  • @Jonatan Yeah, that happens. I'm glad it helped. – Selcuk Jun 07 '16 at 15:14
  • 2
    @Jonatan: Apologies for the loaded question, but why do you want the computer to lie to you? – tmyklebu Jun 08 '16 at 02:29
  • @tmyklebu as i described in the first paragraph of the question, this is a user interface question: if a user enters a number in an input field, he/she should not have to be aware of floating point rounding errors: if I enter 31.45 an an input field, then the input field should read 31.45 after submitting and updating. And if the input field has less decimals, the number should still be the numeric value i entered with correct rounding (31.45 -> 31.5). Everything else would be confusing at best. – Jonatan Jun 08 '16 at 06:23
  • 1
    @Jonatan: Why convert to a different representation in the first place? – tmyklebu Jun 08 '16 at 07:11
  • @tmyklebu yes, that's another take on this: store all settings as strings and parse them right before use. But in this app i think it would cause a lot of problems. For example, one setting may be a temperature. If the user changes preference from Celsius to Farenheit the temperature setting should be updated automatically which would involve some math. Then we cannot depend on the setting to be a string, or at least we would have to parse the string, apply math and format it as a string again and so we have the same rounding problem. – Jonatan Jun 08 '16 at 07:27
  • 2
    @Jonatan: If you want to exactly represent user input, then don't use an inexact representation. For example, use a rational number class, or multiply dollars-and-cents values by 100 to get integers, or a decimal floating point class (if it's adequate for your purpose) or other similar thing. –  Jun 08 '16 at 08:47
  • 2
    @Jonatan: There are also algorithms for "rational reconstruction" that convert floating-point into rational numbers under the presumption that the denominator isn't too big, which could also be suitable for your purpose, if your numbers are in fact rational numbers with denominators not too big. Basically, just do something other than pretend floating point arithmetic good for something it's not good for and complain when you don't get good results. –  Jun 08 '16 at 08:50
  • @Jonatan I was going to delete this answer but waited until your comments are done. I cannot delete it now since it is the accepted answer. If you still think that this is the correct answer you can leave it as it is, but if another solution helped, I suggest you to change the accepted answer. – Selcuk Jun 18 '16 at 05:46
  • @selcuk i truly don't understand why this answer gets that downvoted. This is the only answer that brought up something new that I didn't write about in the original question and on one platform it even was the solution. If you absolutely want me to I will release it anyway. – Jonatan Jun 18 '16 at 06:01
  • @Jonatan No problem, you can keep it if it helped as it may also help future visitors. – Selcuk Jun 18 '16 at 07:11