5
\$\begingroup\$

Very simple question, but important: I was using a floating point number ('float') in C++ to handle movement speed, and it suddenly ran crazy. It turns out the speed increased very fast to 259614846795304004193338175520768.0. That's 33 digits, on a typical (non-double) floating point number. How is that even possible, and can it cause the rest of my program to derail? Or will this number simply be cut down to the 23 bits of significant digits that floating point numbers usually have?

(edit: This is not a bug hunt. I am simply trying to understand how a float can hold such a large number with perfect precision (it's not an exponent, because that would just leave a trail of zeroes after a few significant digits). I am not looking for flaws in my code, just a better understanding of how floating point numbers work, and how this can happen)

EDIT: The solution seems to be a mix of bits from several answers, so I don't know whom to assign as the correct answer. Floating point values are apparently assigned by exponent, which means that some numbers can be amazingly huge without losing precision, as long as they are precise exponents of 2 somehow. Mine was. I thought the size was limited to 23 bits, flat out. As soon as I started using variations of the number that were NOT precise exponents of 2, everything went crazy. Sorry for the radio silence, I had to figure this whole thing out experimentally, once I learned that about floating point numbers from various answers.

I seem unable to close the question. Could someone with proper clearance close it down? And thanks to everyone for the help, I am now far more enlightened regarding floating point numbers!

\$\endgroup\$
12
  • 11
    \$\begingroup\$ Explain the problem with relevant code. \$\endgroup\$ Mar 24, 2017 at 15:08
  • 7
    \$\begingroup\$ Decimal is not binary. You cannot simply assume the number of significant digits in decimal is equal to the number of significant digits in binary. \$\endgroup\$
    – 8bittree
    Mar 24, 2017 at 19:23
  • 3
    \$\begingroup\$ The highest number representable by a 32-bit float is 340282300000000000000000000000000000000 (3.402823e+38), which is much larger than 25961484679530400419333817552076 (2.496148e+31). I think you're thinking that 23 bits of significant digits mean a float can hold less numbers than a 32-bit int, but this isn't how it works because a float also has bits dedicated to an exponent. \$\endgroup\$
    – Pharap
    Mar 24, 2017 at 19:28
  • 5
    \$\begingroup\$ Also this question seems to have two parts, one about how floats are truncated and one involving a bug in your code that increased speed at an absurd rate. Please indicate which you are more interested in because "what's wrong with my code" is different to "how can a float represent a number this large" and it's unclear which question you're asking. \$\endgroup\$
    – Pharap
    Mar 24, 2017 at 19:32
  • 3
    \$\begingroup\$ We don't close questions when they're solved — we want future users with similar uncertainties to be able to find & learn from the content here. The right thing to do is either pick an answer to mark as "accepted," or if you don't feel any of them fully describe the solution, post your own answer describing your new understanding and mark that one "accepted" after the required waiting period. \$\endgroup\$
    – DMGregory
    Apr 10, 2017 at 22:50

5 Answers 5

17
\$\begingroup\$

All single-precision floats have the exact same distribution of bits for the components. Your number, assuming it's properly formatted, is when put into a float the following bit pattern:

0b0111'0101'0100'1100'1100'1100'1100'1101

A IEEE 754 single precision floating point number has one bit for sign, eight for exponent and twenty-three for mantissa, so splitting it up yields:

sign: 0b0
exponent: 0b1110 1010
mantissa: 0b100 1100 1100 1100 1100 1101

The value of a floating point value is (using ^ as exponentiation and interpreting the mantissa as the fractional part of a binary number):

-1 ^ (sign) * 1.mantissa * 2 ^ (exponent - 127)

Putting in your values yields:

v = 1 * 0b1.110011001100110011001101 * 2 ^ (234 - 127)
v = 1 * 1.60000002384185791015 * 2^107

As the bits for your fraction represent the fractional places of the mantissa (1/2th place, 1/4th place, etc.) the last bit of your fraction there is the 1/(2^24)th place, which contrasted with the exponent of 107 means that the nearest number to your number differs by around 2^83.

In summary, this is typically a problem as the magnitudes of the numbers involved in your operations are very likely to have essentially no range in common. Operations on floating point numbers typically "normalize" the numbers to have the same exponent and doing so with your numbers end up obliterating pretty much all the bits involved.

\$\endgroup\$
5
  • 4
    \$\begingroup\$ The user mentions they are using C++, which makes no guarantees that is it IEEE754 compliant. If you want to rely on this behavior you must check std::numeric_limits<float>::is_iec559; . The language itself is otherwise much less strict on floating number formats and a wide range of formats are possible. \$\endgroup\$
    – Vality
    Mar 25, 2017 at 2:27
  • \$\begingroup\$ Hold on... do floating point numbers use exponents of 2 to place points?? I thought they placed the point according to an exponent of 10 (i.e. simply what spot the point would be in, in a base-10 decimal system). The number in question is actually an exact exponent of 2, it just starts at 0.1, not 1 (hence your non-1 root number). If floating point numbers use exponents of 2 when going beyond the mantissa, that might explain it. It would then mean that, according to my lazy headmath, a float that is an exact exponent of 2 could reach from -2^149 to 2^149 or thereabout, does that sound right?? \$\endgroup\$ Mar 25, 2017 at 11:43
  • 2
    \$\begingroup\$ @HenryStone floating point numbers indeed use exponents of 2 rather than 10. But the exponent only has a range of -126 to 127, since it is an 8 bit number and two values are reserved for special numbers. \$\endgroup\$ Mar 25, 2017 at 15:49
  • 1
    \$\begingroup\$ @HenryStone they usually do, but C++ doesn't mandate this, see std::numeric_limits<float>::radix (or FLT_RADIX if we talk about C compatibility). \$\endgroup\$
    – Ruslan
    Mar 25, 2017 at 17:18
  • 1
    \$\begingroup\$ About having no significant bit digits in common, this applies only to operations like addition. Multiplication and such work just as one would expect (until overflow/underflow caused by exponent range). \$\endgroup\$
    – hyde
    Mar 26, 2017 at 9:07
16
\$\begingroup\$

They aren't a problem, per se. But they are often a signpost towards a problem, especially if you're seeing them and you don't expect to.

If you're seeing very large floating point numbers (or very small ones) appear in your data during runtime and you don't expect them to, that's usually indicative of a bug somewhere. Without more information we can't really say, but if you're seeing a particular variable's value increase rapidly it's probably possible for you to break into the debugger at the point where the variable is incremented and work backwards to see what is contributing to the unexpectedly large increase, and then debug from there.

Conversely (for generality), if you're trying to write very long floating point numbers, you should remember that not every floating point value can be accurately represented. Floats have a limited precision and as you get further from zero or try to add more numbers after the decimal you're going to start getting larger and larger deltas between what you wrote and what you actually get in memory.

\$\endgroup\$
1
  • \$\begingroup\$ That's exactly what I worry about. There seems to be no delta, and there should be. The number came about because a function kept doubling the number, but that was easily fixed (I needed to register key input only once per keypress). But the large and precise number still baffles me! \$\endgroup\$ Mar 25, 2017 at 11:35
4
\$\begingroup\$

It's a display issue. Your enormous looking number can be exactly represented by a single-precision floating point number:

13431773 * (2^84) = 259808274926442344861291163680768

That large number is an exact representation of the floating point number, it's just rather misleading, as it implies your floating point number is far more accurate than it really is. A better float->text conversion would be something like 2.5980827e32 which does not pretend to have accuracy that isn't there.

It does seem awfully fast for a movement speed, but I guess it's your decision whether that needs fixing.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I made a comment above on it, but if floats allow using very large exponents of 2, it would make sense. I thought the 23-bit significant digits (the "mantissa", as it's apparently called) was the limit, and the floating point was more or less cosmetically placed according to an exponent of 10 (i.e. the exact point you would place it in a base-10 decimal system). And the speed is huge, it was a bug that is now fixed (single-click acceleration was not cut, so a single click could cause dozens of increases). I simply worry when I get these weird results. And I was curious, of course! \$\endgroup\$ Mar 25, 2017 at 11:48
3
\$\begingroup\$

Very big numbers aren't a problem, neither are very small numbers (unless you approach the limits of representation) what really matters is if you have both big and small numbers because then you start getting serious inaccuracies. This can easily happen in space scale games where strange bugs can start manifesting if you're too far from the origin of your co-ordinate system.

\$\endgroup\$
1
  • \$\begingroup\$ The work done was on actually fixing that problem. But this number just went off the wall, and I felt a need to understand it better. The scaling system is already working, though :) \$\endgroup\$ Mar 25, 2017 at 11:50
1
\$\begingroup\$

Regarding Precision:

As pointed out by @James Hollis, it's possible that the system representing the number is filling in the remaining digits to give an illusion of precision.

The higher an IEEE754 number gets in terms of magnitude, the worse its precision gets. This relationship is near-linear as demonstrated with this diagram (that I shamelessly borrowed from Wikipedia).

IEEE754 Precision/Value Correlation Graph

The typical way this behaviour manifests is by small ammounts of imprecision. An example in C++:

void main(void)
{
    static_assert(sizeof(float) <= 4, "");
    for (float f = 0; f < 1; f += 0.01f)
    {
        std::cout << f << ", ";
    }
    std::cin.get();
}

On my computer this runs fine up until 0.83, after which the numbers deteriorate:

0.839999, 0.849999, 0.859999, 0.869999, 0.879999, 0.889999, 0.899999, 9, 0.909999, 0.919999, 0.929999, ...

These sorts of errors soon mount up. Thankfully in the majority of games these small errors aren't large enough to impact gameplay. The only time the errors become a problem is when trying to do comparisons, because 0.899999 may or may not be equal to 9. Thankfully most languages provide an epsilon value that represents the smallest amount of deviation that can be attributed to expectd error from imprecision.

As @Jack Aidley notes though, this can cause an issue in games where vastly varying scales are present (scales ranging from values in the order of 4 decimal places to values in the millions or billions). In some cases these are unavoidable because (for example) physics involves both incredibly large forces and incredibly small forces. Worst case scenario: you fail to correctly intercept an missile and people die.

In addition, I refer you to some of the interesting exaples of the perculiarities of floating point numbers as found on StackOverflow:

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .