4

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?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 1
    In order to get all 8 of those digits of precision to stay consistent, only the exponent can change. So, 16,777,217 * 2^(x) is going to be represented correctly, but 16,777.217 is likely to lose precision. – Sam Pagenkopf Nov 22 '17 at 08:18
  • I don't understand the withdrawal of the C tag. From what I can tell, more floating point experts prowl around on the C tag than the C++ one. – Bathsheba Nov 22 '17 at 08:40
  • @FelixPalmen: I've done what I think approaches a major edit. – Bathsheba Nov 22 '17 at 08:43
  • @Bathsheba hmm alright. I still don't like the implicit assumption that the explanation for both languages is the same, but it is in this special case, so -- fine :) –  Nov 22 '17 at 08:45
  • @FelixPalmen: Yes I see your point. You could justifiably tag with Java and C# too (if they have the constants defined). It might even be better that way, since those languages mandate IEEE754. In that respect my answer is merely partial. – Bathsheba Nov 22 '17 at 08:46
  • If the application of the numbers addresses "fix point" numbers, an alternative approach could be just to use `int` and simply format the output appropriately. Hence, no floating point issues... (Sorry, it's just an idea.) – Scheff's Cat Nov 22 '17 at 08:52
  • @Scheff: That's no bad idea at all, a 64 bit int in particular is a pretty good way of representing money for example, even if you reserve the final 3 digits for the "pence" (you need 3 to support Tunisian Dinar for example. Bitcoin messes everything up). – Bathsheba Nov 22 '17 at 20:35

4 Answers4

10

(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.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
3

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)
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • Why is this an answer? – Passer By Nov 22 '17 at 09:12
  • @PasserBy - because if I personally were to have arrived here via a google search with roughly the understanding I had *before* asking the question, I would *not* have been able to relate my own question to the answer given by Bathsheba. Hence: Shorter self answer, that I personally think is more to the point. – Martin Ba Nov 22 '17 at 09:27
  • @MartinBa: My counter example was incorrect; have tested yours and pinched it. Have an upvote so I maintain a clear conscience! – Bathsheba Nov 22 '17 at 20:32
  • @chux: I agree with you. What was I thinking? – Bathsheba Nov 22 '17 at 20:33
2

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
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

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.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256