simple C++ question about floats

This code is supposed to add 0.0001 a thousand times to 1, ultimately getting the result ‘1.1000’. However, at the 303rd and 905th iterations, it adds an additional 0.00001 and so it ends up with a wrong result of 1.10002. I suspect some kind of overflow but I’m not sure. So, anyone want to solve this mystery for me? Thanks!


#include <float.h>
#include <iostream.h>
void main(void)
{
 float x;
 int i;
 x = 1.0;
 i = 0;
 while (i <= 1000)
{
     cout << "x = " << x << endl;
     x = x + .0001;
     i++;
}
}

}

“Overflow”? There’s not going to be any FPU overflow with these values. What do you mean an additional 0.00001? Which variable or operation goes wrong? I’d record x and i and i+x each time (maybe that’s how you found out what iteration does what).

Welcome to the world of floating point error. This is an issue with computer hardware, not a programming language, and you’re just going to have to suck it up and deal with it.

You know how in decimal, the only fractions that have terminating representations are of the form n/(2[sup]p[/sup]5[sup]q[/sup]) for some natural p and q? It’s a similar situation in binary, except that you’re looking at fractions of the form n/2[sup]p[/sup] for some natural p. This means that a fraction like, say, .0001 can’t be represented exactly as a floating point number. So the computer adds the best approximation it can get, and over the course of your loop, error propagates. Take a look here for a reasonable discussion of the things that can go wrong with floating point arithmetic.

Oh wow. I wouldn’t have guessed that in a million years, but it actually makes sense. Thanks!

0.0001 is the same thing as 1 / 10000. 10000 is the same as 0x2710. So really we have 0x1 / 0x2710.

If we show a hexidecimal fractional value, the value to the right of the point will half at each step. 0x0.8 is the same as 1/2, 0x0.4 is the same as 1/4, etc. So what is 1 / 0x2710?

Well if we keep a running tally, whereby we on any bit that won’t cause us to go over 1 / 10000…

Bit Value - 0ff/on - Running Tally
1/2 - 0 - 0
1/4 - 0 - 0
1/8 - 0 - 0
1/16 - 0 - 0
1/32 - 0 - 0
1/64 - 0 - 0
1/128 - 0 - 0
1/256 - 0 - 0
1/512 - 0 - 0
1/1024 - 0 - 0
1/2048 - 0 - 0
1/4096 - 0 - 0
1/8192 - 0 - 0
1/16384 - 1 - 0.00006103515625
1/32768 - 1 - 0.000091552734375
1/65536 - 0 - 0.000091552734375
1/131072 - 1 - 0.00009918212890625

Etc.

Essentially, you’re never going to get a binary decimal that exactly equals 0.00001. You’ll just get something with X-bits of accuracy. Over time that’s going to add up to a discrepancy if you do enough math, and care about the level of precision out far enough.

Welcome to binary!

Note that if you care enough about getting the exact right answer, you can just keep a string of bytes where you treat each byte and a decimal digit, and then do all the math “long hand” like you would with pencil and paper, but against your byte digits. There are some libraries that will do this, I believe.

Whilst the question has been answered, I’m surprised you could even get that code to compile on a compiler that isn’t ancient. First of all, main should return an int, iostream.h is deprecated in favour of iostream, and cout and endl are in the namespace std. Also, what’s in float.h?

If you’re learning C++ and your compiler accepted that code without complaint, you should look into a newer compiler (unless you’re under some constraint as to which one you can use). You can get Microsoft’s latest compiler for free off their website if you’re on Windows or use g++ on just about everything else.

HTH.

You should also learn how to figure out how to code loops correctly. Hint: replace 1000 with 0 in the while statement and see how many times the loop runs.

Haven’t learned about “for” statements yet I take it.

It runs on my university’s unix system. The original code isn’t mine and was written on or before 1999 as far as I can tell from the date of the file.

I used g++ to compile it, It did give me a bunch of warnings about deprecation. I do have newer compilers (borland, vc++ 6, vs2k5, codewarrior) but I’m “learning unix” right now so I gotta use g++

When the compile gives a warning, make sure you know why. Just saying, “g++ just spits out all this junk when I compile this code” doesn’t teach you anything about “learning unix”, all it shows is that you are ignoring what is there to be learned, that isn’t in your textbook.

There are two ways to solve this problem.

The first is the method that Sage Rat outlined, where you basically keep track of digits and write routines (or use a library) to do all the math for you. Financial institutions use this type of math, as do a lot of scientific applications.

The second way is to use what is called “fixed point” arithmetic. This is also not natively supported by most CPUs out there. What you do is assume that everything is multiplied by 10,000 (or some other number, quite often a power of 2 since computers like binary). In other words, the integer “1” represents 0.0001. You have to convert 1 into 0.0001 and 2 into 0.0002 etc when you print them out, but internally the numbers are just integers. You get rid of some of the goofy things that happen with floats and addition/subraction, but you also have the typical integer problem of 10 divided by 20 and multiplied by 20 often ends up as zero and not 10 (floats aren’t the only types of numbers you have to be careful of in computer math).

Older 3D games usually used fixed point computations because they needed finer precision than what you could get with integers, and fixed point calculations resulted in integer operations inside the computer, which were significantly faster than floats.

Hmm. You’re right. I should do that. Would you care to recommend some good resources on C++ that I could look at for these kind of things? Google helps but there is so much random, irrelevant information out there to sift through that I find it quite hard to get answers sometimes. In fact, I unsuccessfully tried to find an answer that way before I started this thread.

Hrm… Well kind of. If you were trying to solve this specific problem, then you would be fine to solve it in that way. But fixed point math in for most uses, I believe, and almost definitely gaming didn’t generally have anything to do with preserving decimal values when converted to binary. Usually fixed point math would still have the fractional part encoded as I showed, where you would treat any bits right of a certain point as being at 1/2, 1/4, 1/8, etc. But there would be a “fixed” number of bits used for the fractional value, as opposed to a float where the position of the split between whole number and fraction moves as the whole number grows.

happens to have implemented a fixed point library for doing 3D on a cellphone

It looks like this has been answered fairly well but I want to add that I used to teach programming languages and we taught the concept of model numbers, which are numbers guaranteed to be represented exactly and were based on what could be represented in floating point hardware. This discussion is thorough but a little heavy.

If you’re still learning, warnings are OK, but I have some pretty strict policies about shipping code with warnings (or warnings disabled). If you were programming for Windows, I’d suggest looking up warnings on MSDN, but that won’t help you with g++.

A lot of complicated embedded code will use all sorts of different fixed point systems in the same program without resorting to coding routines for everything and just hardcoding operations. I’ve seen code where in some places fixed point was x10^13 and in others <<8 and the difference was simply noted in comments (if you were lucky). This is convinient especially since in certain embedded applications with several pieces of external computation hardware the actual hardware might expect certain kind of fixed point input and it might be different module to module. Sometimes it’s base 10 sometimes it’s base 2, I’ve even seen base 6 once but I never did figure out what the hell the point of that perversion was.

I don’t know, all of my professors who wanted assignments done in C had a requirement that it must compile with “gcc -ansi -pedantic” with no warnings or it will have to be resubmitted. I think it’s a decent practice even for beginners. Warnings are there for a reason, and even though you might be sick of hearing that you are assigning pointers without a cast it’s that one out of a hundred case where you didn’t dereference something properly that’s going to cause problems.

Yeah… I had one thing I needed to do for a cellphone where depending on the position in the code, I was going to need different precision (or more accurately, in one case I needed to worry about the whole number part going over the top) so I had to use different fixed points. Not pretty, and not recommendable, but twas what needed to happen in that one time in that one application.

Fun in the world of small. Heh.

Thinking in C++ by Bruce Eckel is a free book that should help you learn C++ pretty much from the ground up. You have enough experience you probably won’t need anything else unless you want to do something pretty abstruse and need a serious API and/or language reference. Don’t worry about that for now.

As for the compiler: Try typing ‘info gcc’ at a handy command prompt (an xterm is fine) and poke around. Lines beginning with an asterisk (*) are links to other parts of the documentation: Follow them by moving the cursor (using the arrow keys) down to them and hitting ‘Enter’, go back by pressing ‘p’. That should give you information on command-line options and so on in a way that’s easier to handle than the average manpage.

(If that fails, beat your sysadmin about the head and neck. All copies of gcc and g++ should ship with the info documentation installed and accessable. This is a bit old, but it should at least get you started.)

This isn’t related to the fundamental problem, but you might want to check your loop there - this code adds .0001 to x 1001 times, not 1000. It should be:


int i = 0;

while (i < 1000)
{
     // stuff goes here
     i++;
}

Aside to Dominic Mulligan: Microsoft’s latest C++ tools no longer contain iostream.h, but the compiler will not complain about main being void, even if you specify the strictest warning level. It’s Microsoft. Go figure. :slight_smile: