2

I've been following some online C++ tutorials recently, and have found myself at a loss for what is going on here in one of the code examples. They have used the variable float f = 3.33333333333333333333333333333333333333f; and then output it; setting the precision equal to 16sf. I am aware that floating point variables cannot be accurate to 16sf, but I'm curious as to why the outputted value is 3.333333253860474. I presumed that maybe every digit after the 7th sf might just be junk stored in the memory location used by the variable. To check this, I copied the code used, compiled and ran it myself, andf got the exact same output. Therefore, I am not supposing that their is a reason for these, seeminly random, last 9 digits outputted. Can anyone explain why exactly std::cout << f; gives this output?

M Smith
  • 430
  • 1
  • 7
  • 19
  • 2
    [Closely related](https://stackoverflow.com/questions/588004/is-floating-point-math-broken), potential dupe. – Baum mit Augen Jan 03 '17 at 13:39
  • "*I presumed that maybe every digit after the 7th sf might just be junk stored in the memory location used by the variable.*" How could that be? The memory location used by the variable has to hold the value. How could it hold anything other than the value? – David Schwartz Jan 03 '17 at 14:30
  • This is explained reasonably well in the paragraphs that follow the example, including " Float values have between 6 and 9 digits of precision, with most float values having at least 7 significant digits (which is why everything after that many digits in our answer above is junk)". – molbdnilo Jan 03 '17 at 14:33
  • @molbdnilo: But it's _not_ "junk". – Lightness Races in Orbit Jan 03 '17 at 14:58
  • Keep in mind that "3.33333333333f" is a **text representation** of a floating-point value; it's not a floating-point value, and it has to be converted to a nearby floating-point value. – Pete Becker Jan 03 '17 at 15:11

2 Answers2

7

I assume you were expecting to see 3.333333000000000 instead.

Numbers on computers aren't stored in decimal (base 10), and 3.333333000000000 can't be represented exactly in binary (base 2). The number 3.333333253860474 is the closest value1 which has all threes in the available significant figures.

Imagine if you had a number system which had only increments of 1.1:

|-------|-------|-------|-------|
0      1.1     2.2     3.3     4.4

In this system, if you wanted the number 3, you'd be out of luck, and might get 3.3 instead. If you didn't know off-hand that 1.1 was the smallest "increment" in that system, the .3 might seem "random".

It's a bit like that.

1 I don't know whether it's the closest value, or the closest value upwards. Doesn't really matter either way.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 3
    I think this is the most successful attempt I've ever seen to make floating point math intuitive. Very nicely put! – StoryTeller - Unslander Monica Jan 03 '17 at 13:44
  • @LightnessRacesinOrbit Well, except it strongly suggest that floating point is fixed point... The whole issue is that the intervals are not fixed. The "smallest increment" is proportional to the magnitude of the number. – luk32 Jan 03 '17 at 14:26
  • @luk32: Mm true. Not sure what wording change would fix that without adding undue complexity to the analogy. Any ideas? – Lightness Races in Orbit Jan 03 '17 at 14:46
  • @LightnessRacesinOrbit Honestly, none. I don't see an easy way going form fixed point to floating point. I would suggest to point out that, this is not how floating point works. You can see my answer. I have edited it in the mean time, to show how I see it working. Hopefully it's easy enough. I also used base 10 as an example. Maybe you can serve it better. Feel free to "take inspiration". – luk32 Jan 03 '17 at 14:54
  • @luk32: To be fair, I did say it's "a bit" like that ;) I'll consider adding a disclaimer of some kind, but to be honest between the two answers I think this is probably good enough. – Lightness Races in Orbit Jan 03 '17 at 14:57
  • @LightnessRacesinOrbit I guess. Your answer is, of course, right in the 1st part, the "junk" is related to base of exponent and with 10 there would be no "junk" in decimal notation. If fixed vs floating point "a bit", then it's completely right! =P It is fair to say, that in fixed point, same junk would appear, and the reason would be exactly the same. I am just a little bit scared of people saying this is the "*most successful attempt I've ever seen to make floating point math intuitive.*" But well ¯\_(ツ)_/¯. – luk32 Jan 03 '17 at 15:17
1

Remember that you have a finite space to hold a number. So have to sample the continous space of real numbers. Imagine that you are working in decimal system instead of binary. And you have 10 digits. You can't represent 1.000000000001 exactly. So you have to round it.

Moreover the sampling does not have to have a fixed precision. E.g. how can you represent 3 000 000 000 000 in 10 digits? It's simple, you can use exponential notation it's 3*10^12. See only 5 digits! This is very close to how floating point works. Imagine you have 10 digits, let's fix our base to 10, so we don't waste digits on this. We are left with a*10^b. Changing the ratio of digits between a and b trades off the precision and range available. As an exercise I would suggest using 3/7 and 5/5 and try to represent few numbers using the exponential notation.

If you change the base to 2 you are practically there. The "junk" comes from using the powers of 2 instead powers of 10.

Usually float in c/c++ implements single precision value of IEEE 754 standard, although NathanOlivier is right to point out, that this is left to implementation.

To check how many digits are relevant, c++ offers std::numeric_limits. For the ideone system it's 6. The rest is not junk, it comes from the binary representation of the value. There are appropriate math formulas.

Here is a good explanation of math behind the single precision floating point representation of IEEE 754 standard. Floating point numbers are usually rounded to the nearest representable value. I think this is relevant if you are interested in floating point representation, in spite that technically it is not guaranteed to follow IEEE 754, most implementations across most compilers and languages do so.

You play around with below example (live):

#include <iostream>
#include <limits>
#include <cmath>
using namespace std;

int main() {
    cout << std::numeric_limits<float>::digits10 << '\n';
    cout << std::numeric_limits<float>::is_iec559 << '\n';
    cout.precision(20);
    cout << 3.333333333333333 << '\n';
    for(int i = 0; i < 10; ++i) {
        float f = i;
        cout << "f: " << f << " next:" <<  nextafter(f, f+1.0) << " diff: " << f-nextafter(f, f+1.0) << '\n';
    }
    return 0;
}

As you can see, only 6 signifiacnt digits are guaranteed for back and forth conversion between a float and corresponding base-10 string representation.

You can also observe that the intervals between the closest representable values are not fixed (hence floating point). They scale with the relative value of the number.

luk32
  • 15,812
  • 38
  • 62
  • 1
    We have no idea what `float` implements without asking the implementation. The standard does not require IEEE754. – NathanOliver Jan 03 '17 at 13:42
  • Fair enough, though on most systems `std::numeric_limits::is_iec559` is 1. This is relevant for understanding floating point representation and model. I have made the edits, so hopefully it's clear, and does not contain strong statements which might be false. Thanks for pointing it out. – luk32 Jan 03 '17 at 14:24