4

How many places of precision does a float have between 1.0f and 0.0f such that every value could be uniquely represented?

For example if the first fractional number float couldn't represent 0.13f the answer would be that a float only had 1 place of precision.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 3
    Your first sentence suggests you're looking for unique representation, while your second suggests you're looking for a precise representation. Which is it? I can't represent 0.1 exactly, but I can represent it uniquely. – Joseph Mansfield Sep 11 '14 at 11:25
  • Read http://floating-point-gui.de/ – Basile Starynkevitch Sep 11 '14 at 11:54
  • @JosephMansfield There are an infinite number of real numbers between 0.0 and 1.0. Could every number with 2 places of precision be converted to an `int` and back; such that the original `float` is preserved? So in the example question if I can represent: `0.00F`, `0.01F`, `0.02F`, `0.03F`, `0.04F`, `0.05F`, `0.06F`, `0.07F`, `0.08F`, `0.09F`, `0.10F`, `0.11F`, `0.12F`, and `0.14F` then I cannot uniquely represent `0.13F`. That would means that if I did: `float foo = 0.13F;` `foo` would have the same representation as it would for `0.12F` or `0.14F`. – Jonathan Mee Aug 24 '15 at 11:13

2 Answers2

7
std::numeric_limits<float>::digits10

From http://en.cppreference.com/w/cpp/types/numeric_limits/digits10

The standard 32-bit IEEE 754 floating-point type has a 24 bit fractional part (23 bits written, one implied), which may suggest that it can represent 7 digit decimals (24 * std::log10(2) is 7.22), but relative rounding errors are non-uniform and some floating-point values with 7 decimal digits do not survive conversion to 32-bit float and back: the smallest positive example is 8.589973e9, which becomes 8.589974e9 after the roundtrip. These rounding errors cannot exceed one bit in the representation, and digits10 is calculated as (24-1)*std::log10(2), which is 6.92. Rounding down results in the value 6.

Edit2: This shows that the number is not 7 but only 6 digits for any float, just like the std::numeric_limits<float>::digits10 call will tell.

float orgF = 8.589973e9;
int i = orgF;
float f = i;
assert(f == orgF);

This will fail as the roundtrip changes the value.

So if we are only looking for numbers between 1.0 and 0.0, positive the answer is 7, as the lowest positive number which has a problem is 8.589973e9.

Surt
  • 15,501
  • 3
  • 23
  • 39
  • @Surt I can't understand this statement, can you explain it to me? "But relative rounding errors are non-uniform and some floating-point values with 7 decimal digits do not survive conversion to 32-bit float and back." I assume this is referring to the earlier statement: "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." – Jonathan Mee Sep 12 '14 at 10:54
  • 1
    The round trip `float orgF = 8.589973e9;int i = orgF; float f= i; assert(f == orgF);` Can go wrong – Surt Sep 12 '14 at 10:59
  • Example added by you doesn't directly map to OP's question. – Mohit Jain Sep 13 '14 at 12:48
  • The example was in response to the OP question in his comment to this. – Surt Sep 13 '14 at 13:25
  • My point is: `relative rounding errors are non-uniform` and OP wants to ask the `float between 1.0f and 0.0f` and this example talks about a number in range 8589973000. How does it prove (I am not counter-arguing and believe your answer 6 is correct and well referenced) that same number of digits are true for range 1.0f to 0.0f? After your edit, you need to give a little more details how number of significant digits are **not** dependent on exponent. – Mohit Jain Sep 15 '14 at 08:01
4

If I understood your question correctly, the answer is 6.

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
using namespace std;

int main() {
    int i = 10;  /* Number of distinct number after 1 place of decimal */
    int k = 1;   /* Number of decimal places */
    for(;/* ever */;)
    {
        float prev = 0.0f;
        for(int j = 1; j <= i; ++j)
        {
            stringstream ss;
            ss << "0.";  /* Prepare stream with 0. followed by k digit number with leading zeroes */
            ss << setfill('0') << setw(k) << j;
            float next; /* Read the number */
            ss >> next;
            if(prev == next) return 0;  /* If previous number and current number can not be distinguished */
            prev = next;
        }
        cout << "Works for " << k << " places" << endl;
        i *= 10; /* 10 times more tests to be conducted for 1 more decimal places */
        k++;     /* Try for more decimal places */
    }
    return 0;
}

What does the code do

1. Set precision to 1 place after decimal
2. Compare 0.0 with 0.1, 0.1 with 0.2 .... 0.8 with 0.9 and 0.9
   with 1.0, if any of these are equal (not distinguish), exit.
   Otherwise print Works for 1 place.
3. Set precision to 2 places after decimal
4. Compare 0.00 with 0.01, 0.01 with 0.02 .... 0.98 with 0.99 and 0.99
   with 1.00, if any of these are equal (not distinguish), exit. Otherwise
   print Works for 2 places.
5. Repeat similar steps for 3 and more digits unless you exit
Mohit Jain
  • 30,259
  • 8
  • 73
  • 100
  • +1 a very pragmatic way to answer this question. I wish I could accept this as the answer but it appears that the answer of @Surt is a bit more, as you say "Well written." – Jonathan Mee Sep 12 '14 at 10:59