PDA

View Full Version : Trouble with output of float values using NSLog




bahlquist
Oct 16, 2010, 08:46 PM
(This is a restatement of a problem brought up in a previous thread.) This program outputs two numbers each time a button is pushed. The first number is random. The second number should be 1 greater than the first. However, this is not the case (as can be seen by pushing the button more than once).

BATester.h:

#import <Cocoa/Cocoa.h>

@interface BATester : NSObject {
}
- (IBAction)button:(id)sender;
@end




BATester.m:

#import "BATester.h"

@implementation BATester
- (IBAction)button:(id)sender {
NSNumber* theNumb;
theNumb=[[NSNumber numberWithFloat:rand()] retain];
NSLog(@"%f and %f", [theNumb floatValue],([theNumb floatValue]+1));
}
@end

Here is my output:

[Session started at 2010-10-16 18:32:35 -0700.]
2010-10-16 18:32:42.181 Tester[419:10b] 16807.000000 and 16808.000000
2010-10-16 18:32:43.275 Tester[419:10b] 282475264.000000 and 282475264.000000
2010-10-16 18:32:45.762 Tester[419:10b] 1622650112.000000 and 1622650112.000000
2010-10-16 18:43:34.338 Tester[419:10b] 984943680.000000 and 984943680.000000



The first time the button is pushed, no problem, but all subsequent pushes print out the same number twice. What is going on?

(If you are worried about memory leaks, please address this issue in my post entitled "Memory leaks".)



chown33
Oct 16, 2010, 09:24 PM
The cases where it fails to add 1 are all because you've exceed the precision (number of significant digits) of a float.

Change the type to double (i.e. doubleValue) and it will work.

Better still, use the correct simple type, which is int (the type returned by rand()), and you can eliminate most of the pointless code, including eliminating memory leaks.

- (IBAction)button:(id)sender {
int number = rand();
NSLog(@"%d and %d", number, number+1);
}

The basic problem here is that you're applying brute force and ignorance in order to obtain immediately gratifying but ultimately vacuous results. What you should be doing instead is learning fundamentals like different integer and floating-point types, and how each one works in different circumstances.

I've already said this once, but I'll repeat it: you should be learning from a book.

Almost any book will have adequate guidance and will introduce things at a suitable time. All that's happening now is you're flailing around in deep water, with little or no actual knowledge of what you're doing or why. In short, you're drowning yourself when you should be taking swimming lessons.

lee1210
Oct 16, 2010, 09:47 PM
I will add that float is very, very rarely the right type to use for anything, ever. It's 2010. We have multi-ghz 64-bit CPUs and GBs of RAM in almost every machine. Saving 4 bytes and sacrificing precision is not generally a good plan unless you are developing in an environment where the computational demands of double precision math are too high (or unavailable), or the memory requirements are so tight that you can't afford the 4 bytes. If you are in that situation, you know it. If you're not, use double.

-Lee

bahlquist
Oct 16, 2010, 09:58 PM
The cases where it fails to add 1 are all because you've exceed the precision (number of significant digits) of a float.

Change the type to double (i.e. doubleValue) and it will work.



Yes, that fixes this problem. So, when I attempt to do the operation "+", and I exceed the precision, why does it return the number it does? It is not choosing the first operand ([numb floatValue]) to return by default, for I switched the order around ((1+ [numb floatValue]) instead of ([numb floatValue]+1)) and I got the same result. I couldn't find an answer to this in my book.

lee1210
Oct 16, 2010, 11:35 PM
If you really want to, you can research IEEE-754 32-bit floating point representations, and see what the representations of these values will be. Once you have that, you can look into how addition is performed on these. The ultimate result will be that the values are adjusted such that the exponent is the same, so simple addition can be performed on the significand. When 1 is adjusted to have the same exponent as your very large numbers, it will be rounded to 0. This yields the behavior you observe.

Using double would help this situation, but not remedy it completely. Binary math with floating points is inherently imprecise. That is why, as in this case, that fixed point math (which can be done exactly in binary) should be used when at all possible.

-Lee

gnasher729
Oct 17, 2010, 04:40 AM
I see this all the time, beginners using "float" instead of "double", and I am really wondering why. Is there any book recommending this (in which case we should add it to a list of "books never to be opened"), or is there some teacher advising you to use float (in which case he or she should be slapped) or did you just use the first one that came to mind without any clue about the difference?

To the OP: Google for "N1256" which should find the document "N1256.pdf" which contains the latest free draft of the C99 language standard. Then search for "float".

Finally, saving two keystrokes to write "theNumb" instead of "theNumber" is really a bad trade-off. numb = "deprived of the power of sensation", number = "arithmetic value, quantity or amount". Two letters saved, meaning of the word destroyed.

Finally finally, you realise your code has a memory leak?

jared_kipe
Oct 17, 2010, 12:02 PM
Finally finally, you realise your code has a memory leak?

A particularly pointless memory leak as he used +numberWithFloat: then retain instead of just letting it get autoreleased later.

Floats are still used. Specifically places where you need a whole lot of them (like in large arrays say in OpenGL), or places where you won't notice the lack in precision (CGFloat on iOS and i386 MacOS, while x86_64 MacOS CGFloats are double precision (64bit floats))

chown33
Oct 17, 2010, 12:48 PM
It is not choosing the first operand ([numb floatValue]) to return by default, for I switched the order around ((1+ [numb floatValue]) instead of ([numb floatValue]+1)) and I got the same result.

Operand order has no effect in this case. The arithmetic expression only has a single operator, so precedence plays no part.

I couldn't find an answer to this in my book.
Which book? Title, author, edition.

Many beginner books won't explain the precision limits of the float type. Instead, they would just use double and avoid explaining anything.

Is this random-number program an exercise in your book? Does it tell you to use NSNumber, or did you decide that yourself? How did the choice of 'float' come about?

bahlquist
Oct 17, 2010, 02:16 PM
If you really want to, you can research IEEE-754 32-bit floating point representations, and see what the representations of these values will be. Once you have that, you can look into how addition is performed on these. The ultimate result will be that the values are adjusted such that the exponent is the same, so simple addition can be performed on the significand. When 1 is adjusted to have the same exponent as your very large numbers, it will be rounded to 0. This yields the behavior you observe.

Using double would help this situation, but not remedy it completely. Binary math with floating points is inherently imprecise. That is why, as in this case, that fixed point math (which can be done exactly in binary) should be used when at all possible.

-Lee

Thank you for your concise and helpful answer.

lee1210
Oct 17, 2010, 03:28 PM
Thank you for your concise and helpful answer.

Sure. Because I was feeling bored and wanted to write some code that wasn't work-related:
#include <stdio.h>
#include <inttypes.h>

void print_float_info(float);
void print_sig(uint32_t);

int main(int argc, char *argv) {
float a,b,c,d,one;
uint32_t *show_a,*show_b,*show_c,*show_d,*show_one;
one = 1.f;
a = 16807.f;
b = 282475264.f;
c = 1622650112.f;
d = 984943680.f;
show_a=(uint32_t *)&a;
show_b=(uint32_t *)&b;
show_c=(uint32_t *)&c;
show_d=(uint32_t *)&d;
show_one=(uint32_t *)&one;
printf("a: %X\tb: %X\tc: %X\td: %X\tone: %X\n",*show_a,*show_b,*show_c,*show_d,*show_one);
print_float_info(a);
print_float_info(b);
print_float_info(c);
print_float_info(d);
print_float_info(one);
}

void print_float_info(float a) {
uint32_t *show_a;
uint32_t significand;
int32_t exponent;
char sign;
show_a = (uint32_t *)&a;
sign = (0x80000000 & *show_a)?'-':'+';
exponent = ((0x7F800000 & *show_a) >> 23) - 127;
significand = (0x7FFFFF & *show_a);
printf("%f decimal intrepretation. Sign: %c Exp: %d Sig: ",a,sign,exponent);
print_sig(significand);
printf("\n");
}

void print_sig(uint32_t a) {
int pos;
uint32_t bits[23] = {0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,0x100,0x200,0x400,0x800,0x1000,0x2000,0x4000,0x8000,0x10000,0x2 0000,0x40000,0x80000,0x100000,0x200000,0x400000};
printf("1.");
for(pos=22;pos>=0;pos--) {
printf("%c",(bits[pos]&a)?'1':'0');
}
}

This doesn't deal with 0, subnormals, infinities, etc. but it does pull apart a float, show you the components, and it might help you see why the 1 issue might be problematic. Basically if the exponent is greater than 23, that means that there are "trailing zeros" past what's actually stored in the significand past which the binary point will get moved. That means that there would not be enough precision to store something in the bit just left of the binary point.

In fact, it's quite possible that the float values you were getting were already very rounded from what actually came back from rand(), but if you weren't displaying what came back from rand you might not have known about it.

jared_kipe pointed out a few times floats get used. These examples are APIs where the designer (who knows a lot more than most of us) has made a specific decision about the sacrifice being made. Until you're at that level, you should never actually need float.

-Lee