2

Here's the code:

typedef std::numeric_limits<float> fl;

int main()
{   
    std::cout.precision(100);    

    float f1 = 9999978e3;
    std::cout << f1 << " (9999978e3 decimal number stored as binary)" << std::endl;

    float f2 = 9999979e3;
    std::cout << f2 << " (9999979e3 decimal number stored as binary)" << std::endl;

    float f3 = 9999990e3;
    std::cout << f3 << " (9999990e3 decimal number stored as binary)" << std::endl;

    std::cout << "-----^" << std::endl;
}

which prints:

9999978496 (9999978e3 decimal number stored as binary)
9999978496 (9999979e3 decimal number stored as binary)
9999989760 (9999990e3 decimal number stored as binary)
-----^ 

The decimal numbers stored as binary correctly keep the 6th digit for 9999978e3 and 9999979e3 (which is 7), but the 6th digit of 9999990e3 is 8 and not 9.

Shouldn't floating point precision always guarantee the first 6 digits?

Yes, if I round 89 I got 9, but that's not the same; this will work/make sense only "visually".

Later (on processing numbers), when I'll apply math on that value, it'll work on a xxxxx8 number (9999989760), and not on a xxxxx9. +1 at that magnitude.

JJJ
  • 32,902
  • 20
  • 89
  • 102
markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 3
    6 digits are definitely not guaranteed – Mad Physicist Nov 06 '17 at 13:54
  • 3
    "If a decimal string with at most 6 significant digits is converted to IEEE 754 single-precision representation, and **then converted back to a decimal string with the same number of digits**, the final result should match the original string." https://en.wikipedia.org/wiki/Single-precision_floating-point_format – melpomene Nov 06 '17 at 13:56
  • Its a rough estimate thats easy to remember, not a steadfast rule – Nick is tired Nov 06 '17 at 13:56
  • 2
    The 6 digits are guaranteed when you take into account rounding. I don't understand why you say that you don't accept this in your question. – interjay Nov 06 '17 at 13:57
  • @interjay because when you apply "math" at that magnitude, there's no rounding :O – markzzz Nov 06 '17 at 13:59
  • 1
    @markzzz What do you mean by "apply math"? And why do you think there's no rounding? – melpomene Nov 06 '17 at 14:00
  • 2
    @markzzz When you apply "math" there are no decimal digits either, so I don't understand the significance. The number is still closer to 9999990e3 than to 9999980e3. – interjay Nov 06 '17 at 14:00
  • 1
    Nicely put question by the way. Why am I the only upvoter? – Bathsheba Nov 06 '17 at 14:06
  • 1
    If you don't round, you can get *zero* matching digits. For example, if you convert 0.999999999 to floating point, and back, you'll get 1.0, which has zero matching digits. Rounding is mandatory here. – geza Nov 06 '17 at 14:19
  • You can play with this IEEE-754 Floating Point Converter https://www.h-schmidt.net/FloatConverter/IEEE754.html – Picaud Vincent Nov 06 '17 at 14:35
  • @interjay: if "6 digits" were guarantee, I aspect that the first 6 digits I got ARE THOSE, not coming from rounding. I always see them considered "preserved". Such as if I have `1.00005`, all of them respect the magnitude. World destroyed http://coliru.stacked-crooked.com/a/ccb499739861eacb – markzzz Nov 06 '17 at 15:31
  • Then you misunderstand the guarantee. The point of floating point is to be as accurate as possible given the limitations. Your version of the guarantee would be less accurate. Maybe you're using binary floating point for something which shouldn't be using it. – interjay Nov 06 '17 at 16:43
  • @interjay: I think I got the point. But I believe this works only if `round(a) + round(b) = round(a + b)`. Is that guarantee? If so I understand why I don't need "exact 6 digits" but I can rely on rounding ULP... – markzzz Nov 07 '17 at 09:20
  • No, that isn't guaranteed. When you do operations like addition you lose some accuracy, so after enough operations you won't have the 6 digits accuracy even after rounding. – interjay Nov 07 '17 at 09:48
  • @interjay: that's what I'm talking about. If I have a 0.xxxxxY decimal number and another one as 0.xxxxxZ (which the 6° digits for both are get by rounding, since its not preserved by internal binary storing after the conversion to decimal again), I can't rely later on their rounding, because they are not (in fact) keeping "that" magnitude when I'll elaborate them. So if I (think to have) a number with a value at some magnitude and another with another value at the same magnitude, when I sum it, the magnitude value can be not respected. – markzzz Nov 07 '17 at 09:53
  • Your version of the guarantee (based on truncation) would also not be preserved after addition, so it doesn't actually get you anything. – interjay Nov 07 '17 at 11:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/158408/discussion-between-markzzz-and-interjay). – markzzz Nov 07 '17 at 11:24

2 Answers2

9

Not in the way you think it's guaranteed, no.

By way of offering a counter-example, for an IEEE754 single precision floating point, the closest number to

9999990000

is

9999989760

What is guaranteed is that your number and the float, when both are rounded to six significant figures, will be the same. This is be the value of FLT_DIG on your platform, assuming it implements IEEE754. E.g. the closest float number to 9999979000 is 9999978496.

See http://www.exploringbinary.com/floating-point-converter/

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Really? https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32 – markzzz Nov 06 '17 at 13:58
  • 2
    "This gives from 6 to 9 significant decimal digits precision. If a decimal string with at most 6 significant digits is converted to IEEE 754 single-precision representation, and then converted back to a decimal string with the same number of digits, the final result should match the original string. If an IEEE 754 single-precision number is converted to a decimal string with at least 9 significant digits, and then converted back to single-precision representation, the final result must match the original number." Wrong? – markzzz Nov 06 '17 at 13:58
  • @interjay: Yes, I've made the answer a little more mature - see the penultimate paragraph. – Bathsheba Nov 06 '17 at 14:03
  • 1
    I think the Wikipedia quote should say or should be read as ".. and then converted back to a decimal string with the same number of **significant** digits .." It implies that because otherwise it just doesn't make any sense. – JJJ Nov 06 '17 at 14:08
  • 1
    Thus "6 significant digits" **ONLY** means that **ONCE YOU ROUND** the converted `decimal-binary-decimal` value, the starting decimal is equal to the converted-back decimal. So basically it guarantees that ULP between the two decimal is around +-0.5? – markzzz Nov 06 '17 at 15:46
  • 1
    Re: “… your number and the `float`, when both are rounded to seven significant figures… will be the same.” Seven is not guaranteed. With 32-bit binary IEEE-754, `printf("%.7g", 9.999979e9f);` yields “9.999978e+09”. That is why `FLT_DIG` in C is 6 when IEEE-754 is in use. – Eric Postpischil Nov 06 '17 at 15:53
  • @EricPostpischil: Naturally, you're correct. I'm amended and have wiki'd this due to the value of your comment. – Bathsheba Nov 06 '17 at 16:11
  • Wikipedia is correct. The problem is the decimal print library used in C libm. If you could use a library that prints the minimal number of digits such that the decimal is re-interpreted as the same float, then you would get the expected result. I'm not even sure that the float is not converted to double when serializing with <<... – aka.nice Nov 07 '17 at 08:46
2

You will never get a precise number of digits base 10 because the number isn't be stored base 10 and most base 10 fractions have no perfect representation in base 2. There will almost always be a rounding error, and through repeated adding you can magnify that roundoff error.

For example, 1/5 has this binary pattern:

111110010011001100110011001101

We only care about the last 23 bits (the mantissa) for what you're talking about...

10011001100110011001101

Notice the repeating pattern of 1001. To truly represent 0.2 that pattern would have to repeat forever instead of ending with a roundoff of 1.

Multiply that number by enough and the roundoff error is magnified.

If you need to have a precise number of decimal digits, then use integer math and handle the rounding yourself in the case of division in a manner satisfactory to you. Or use a bigint library and rational numbers and end up with huge fractions that take forever to compute, but you'll have infinite precision. Though, of course, any number that can't be represented as a rational like the sqrt(6) or pi will still have a roundoff error.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194