Martin Ba
Martin Ba

Reputation: 38861

C++ `digits10` is 6 for IEEE float, but the first non-representable integer already has 8 digits?

C++'s std::numeric_limits<float>::digits10, is described on cppref as such:

The value of std::numeric_limits<T>::digits10 is the number of base-10 digits that can be represented by the type T without change, that is, any number with this many decimal digits can be converted to a value of type T and back to decimal form, without change due to rounding or overflow.

A similar description exists for the C cousin FLT_DIG.

The value given is:

float     FLT_DIG /* 6 for IEEE float */

However, it is shown here on S.O. that all integers up to 16,777,216 (224) are exactly representable in a 32 bit IEEE float type. And if I can count, that number has 8 digits, so the value for digits10 should actually be 7, now shouldn't it?

Rather obviously, I misunderstand something about digits10 here, so what does this actually tell me?


Practical applicability:

I was asked if we could store all numbers from 0.00 - 86,400.00 exactly in an IEEE 32 bit float.

Now, I'm very confident that we could store all numbers from 0 - 8,640,000 in an IEEE 32 bit float, but does this hold for the same "integer" range shifted by 2 digits to the left?

Upvotes: 4

Views: 311

Answers (4)

chux
chux

Reputation: 154315

Sometimes it is easy enough to look for counter examples.

#include <stdio.h>
#include <string.h>

int main(void) {
  int p6 = 1e6;
  int p7 = 1e7;
  for (int expo = 0; expo < 29; expo++) {
    for (int frac = p6; frac < p7; frac++) {
      char s[30];
      sprintf(s, "%d.%06de%+03d", frac / p6, frac % p6, expo);
      float f = atof(s);
      char t[30];
      sprintf(t, "%.6e", f);
      if (strcmp(s, t)) {
        printf("<%s> %.10e <%s>\n", s, f, t);
        break;
      }
    }
  }
  puts("Done");
}

Output

<8.589973e+09> 8.5899735040e+09 <8.589974e+09>
<8.796103e+12> 8.7961035080e+12 <8.796104e+12>
<9.007203e+15> 9.0072024760e+15 <9.007202e+15>
<9.223377e+18> 9.2233775344e+18 <9.223378e+18>
<9.444738e+21> 9.4447374693e+21 <9.444737e+21>
<9.671414e+24> 9.6714134744e+24 <9.671413e+24>
<9.903522e+27> 9.9035214949e+27 <9.903521e+27>
<1.000000e+28> 9.9999994421e+27 <9.999999e+27>  This is an interesting one

Upvotes: 2

Martin Ba
Martin Ba

Reputation: 38861

As the other answer and comment establishes, digits10 covers all "exponent ranges", that is it has to hold for 1234567 as well as for 1.234567 and 12345670000 -- and this only holds for 6 digits!

Counter example for 7 digits:

  • 8.589,973 e9 vs. 8.589,974 e9 (from cppref example)

Upvotes: 3

chux
chux

Reputation: 154315

Another point of view:

Consider between each pair of powers-of-2, a float like IEEE binary encodes 223 values distributed linearly.


Example: Between 20 and 21 or 1.0 and 2.0,

The difference between float values is 1.0/223 or 10.192e-06.

Written in text form "1.dddddd", a 7 digit number, the numbers have a difference of 1.000e-06.

So for every step of a decimal text number, there are about 10.2 float.
No problems encoding these 7 digit numbers.
In this range, no problems encoding 8 digits either.


Example: Between 223 and 224 or 8,388,608.0 and 16,777,216.0.

The difference between float values is 223/223 or 1.0.

The numbers near the low end written in text form "8or9.dddddd*106", a 7 significant digit number, have a difference of 1.0.

No problems encoding these 7 digit numbers.


Example: Between 233 and 234 or 8,589,934,592.0 and 17,179,869,184.0,

The difference between float values is 233/223 or 1,024.0.

Numbers near the low end written in text form "8or9.dddddd*109", a 7 significant digit number, have a difference of 1,000.0.

Now we have a problem. From 8,589,934,592.0, then next 1024 numbers in text form only have 1000 different float encoding.


7 digits in the form d.dddddd * 10expo is too many combinations to uniquely encode using float.

Upvotes: 1

Bathsheba
Bathsheba

Reputation: 234785

(Restricting this answer to IEEE754 float).

8.589973e9 and 8.589974e9 both map to 8589973504. That's a counter-example for an assertion that the 7th significant figure is preserved.

Since no such counter-example exists on the 6th significant figure, std::numeric_limits<float>::digits10 and FLT_DIG are 6.

Indeed integers can be represented exactly up to the 24th power of 2. (16,777,216 and 16,777,217 both map to 16,777,216). That's because a float has a 24 bit significand.

Upvotes: 10

Related Questions