1

The output of the following two lines seem to show that NSString's stringWithFormat statement does not round numbers consistently. It seems to round up correctly from 0.75 to 0.8. But it rounds 0.25 down to 0.2. Here is the code.

    NSString *sRoundedScore = [NSString stringWithFormat:@"%.1f", fScore];
    NSLog(@"\r\n score before rounding: %f, rounded score: %@", fScore, sRoundedScore);

When fScore is assigned to be 0.25, the output is:

score before rounding: 0.250000, rounded score: 0.2

When fScore is assigned to be 0.75, the output is:

score before rounding: 0.750000, rounded score: 0.8

I had to begin using NSNumberFormatter to get this to round consistently up. Why though does stringWithFormat produce this variation in results?

Alyoshak
  • 2,696
  • 10
  • 43
  • 70

2 Answers2

3

This answer goes back to "printf" functionality

Bankers Rounding:

It's important to know this is more stating that you want to show one decimal place on a Floating point integer.

Since you are stating one decimal point it is examing the second decimal to decide where to round. The rounding logic for %.f format'er works like so: if your number directly ahead is under 4 then 5 rounds down if you your number directly ahead is 4 and above 5 rounds up.

Reason of this: http://linuxgazette.net/144/misc/lg/a_question_of_rounding_in_issue_143.html: "For the GNU C library, the rounding rule used by printf() is "bankers rounding" or "round to even". This is more correct than some other C libraries, as the C99 specification says that conversion to decimal should use the currently selected IEEE rounding mode (default bankers rounding)."

found from this answer: printf rounding behavior for doubles

If you want to do a common rounding round inline above use the c function round() with a little math to do the trick or you can simply use NSNumberFormatter as you stated.

Rounding numbers in Objective-C

Community
  • 1
  • 1
Ben Coffman
  • 1,750
  • 1
  • 18
  • 26
  • So why does it round the 0.25 down, if it rounds 0.75 up the way you suggest? That's the question I am asking here. Why up in some cases but down in others. – Alyoshak Jul 18 '13 at 14:57
  • .25 rounds down because the .2 is below .5 (or .45) if you do .55 it will round up to .6 however if you are under .5 and you do .26 if will round up. I'm sure there is some low level logic to why it does this, but it's more important to know that it does. – Ben Coffman Jul 18 '13 at 15:10
  • Thx Ben. I'm not sure I agree that it is more important to know *that* it does this than why it does it. As helpful as your comment is, I'm hoping to get an explanation of the behavior not simply more examples of the behavior. If I choose to format to one decimal place the results seem arbitrary. David's answer is promising, but I can't yet confirm this is what's happening nor why the implementation would store 0.25 as 0.2499999999 but not store 0.75 as 0.74999999. – Alyoshak Jul 18 '13 at 15:23
  • Please see my updated answer to address your core knowledge understanding of why this is happening. – Ben Coffman Jul 18 '13 at 15:40
1

The root problem is that when you see 0.25, the number is actually something more like 0.2499999999999. There are some standard techniques to get the rounding you want. In this particular case, you can multiply the float by 100.0f, then use lroundf, and print the long:

float foo = 0.25f;
NSLog(@"First %f", foo);
NSLog(@"Round %.1f", foo);
foo += 0.0000001;
NSLog(@"Second %f", foo);
NSLog(@"Round %.1f", foo);
//2013-07-18 15:36:09.021 EmailAddressFinder[12618:303] First 0.250000
//2013-07-18 15:36:09.022 EmailAddressFinder[12618:303] First 0.2
//2013-07-18 15:36:09.022 EmailAddressFinder[12618:303] Second 0.250000
//2013-07-18 15:36:09.022 EmailAddressFinder[12618:303] Second 0.3

foo = 0.25f;
foo *=100;

long l = lroundf(foo);
NSLog(@"l=%ld", l);
NSLog(@"0.%0.2ld", l); // .2=="print two places", 0=="add a leading 0 if needed"
//2013-07-18 15:37:24.424 EmailAddressFinder[13474:303] 0.25

You will need to adjust for ranges etc. Whenever I use US currency, I alway round to integral cents this way, and use a small method to give me a dollar.cents string back from just cents, by using this same technique.

EDIT: I see you awarded an answer - anyone who uses the behavior of printf to determine what their user is going to see is going to have problems at some point. Cooerce the numbers into something you know for sure and you will have robust and portable code.

David H
  • 40,852
  • 12
  • 92
  • 138
  • I tried to use the style of formatting you used there with your printf, but Xcode doesn't like it (I'm in a .m file, not .mm). Won't compile. I'd mess around more with it, but this question has to do with the stringWithFormat method of the NSString class so perhaps you can show me what you mean with it. You're saying that 0.25 is actually stored as 0.249999999, and that would certainly explain part of the behavior. But it doesn't explain why 0.75 is rounded up. It would indeed be enlightening if we could see what is actually being stored for both 0.25 and 0.75. – Alyoshak Jul 18 '13 at 15:07
  • Floating point is a complex subject - values are not kept in base 10 but base two, in two parts, and translating them into a number you see involves lots of complex decisions. – David H Jul 18 '13 at 19:32
  • Makes sense. What didn't make sense was why the translation process involving a slight decrease in value in one case but a slight increase in another. I thought I might have a bug on my hands, something I perhaps should report, but had no plausible explanation until the banker's rounding theory was put forward. – Alyoshak Jul 18 '13 at 23:35
  • In case you didn't see, the number is NOT exact - look at my code - I added a small fudge factor to "force" the number higher, and it failed. You can spend a lifetime trying to get conversions down, and its still not enought time. Use what you want - look at my points and yours - if you want to do it right, take control, don't rely on obscure behavior. Its your choice in the end. – David H Jul 18 '13 at 23:51
  • Thanks David. (I saw your points at the beginning. Very impressive.) – Alyoshak Jul 19 '13 at 03:35