3
votes

For single precision, min digits guarantee are 6.

i.e. both 9999978e3 and 9999979e3 will "converge" to 9999978496. So whatever decimal I'll use, 6 digits are always guarantee by the single precision floating point math (at least, for IEEE 754).

The same I think apply for double precision, but the min value should be 15. I can't find a decimal number that proof this, as for above that use single precision.

Can you give to me one? Or how would you retrieve it?

4
Many articles on this site, here is one that looks relevant: exploringbinary.com/…Richard Critten
Your question appears to be confusing two notions. One notion is the number of decimal digits that are necessary to uniquely identify a float or a double. You use of the word “guarantee” seems to indicate that you are interested in the number of correct digits when trying to represent a decimal number in float or double. Technically, this number is zero, for instance both formats represent the decimal number 0.999999999999999999999999995 as 1.0, getting no decimal digit correct.Pascal Cuoq
You could iterate through all possible floats using en.cppreference.com/w/cpp/numeric/math/nextafter. I can't see why the outliers wouldn't be evenly distributed in exponential space, so you should be able to yield a counter-example pretty quickly.Bathsheba
It took a while but I've detailed the most tractable (in my opinion) range of numbers that serve as counterexamples.Bathsheba
@EricPostpischil: Indeed, although I think you'd reach a counter-example pretty quickly. I still maintain they are distributed regularly in exponential space.Bathsheba

4 Answers

4
votes

Both 9007199254740992 and 9007199254740993 are 16 digit numbers, and both have the value 9007199254740992 when stored as an IEEE754 double.

i.e. the 16th digit of 9007199254740993 is a joke.

My inspiration behind picking this example is that 9007199254740992 is the 54th power of 2, just after the number of bits in the significand of an IEEE754 double type, and the first decimal digit happens to be a 9. So none of the odd numbers above this are representable, despite having only 16 digits!


Sticking to IEEE754 double precision, if you want an example in the range 0 to 1, then start with the dyadic rational 0.75 and add a value of the order 1e-16. Quickly, you'll stumble on 0.7500000000000005 and 0.7500000000000006, which are both 0.75000000000000055511151231257827021181583404541015625

1
votes

I've elaborated (thanks to @Bathsheba tips) an algorithm that, starting from a decimal part and increment it by needed digit (16th in my case) will found (for the following 10000 decimal) decimals that will collide to the same binary double precision IEEE754 representation. Feel free to adjust it:

#include <iostream>

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

    long long int decimalPart = 7500000000000005;
    double value, temp = 0.0;

    // add 1e-16 increment
    for(int i = 0; i < 10000; i++) {
        value = decimalPart / 1e16;

        // found
        if(temp == value) {
            std::cout << "decimal found: 0." << decimalPart << std::endl;
            std::cout << "it collides with: 0." << decimalPart - 1 << std::endl;
            std::cout << "both stored (binary) as " << value << std::endl << std::endl;
        }        

        decimalPart += 1;
        temp = value;        
    }
}
1
votes

Can you give to me a 16 digits (or more) decimal number that converted in double precision floating point round correctly only at 15th?

Such numbers are not rare so easy enough to try various strings limited to the range of interest.

Over a wide range of 16 digit decimal text values, about 10% failed. All failures began with a leading digit of '4' or more - not surprising.

// Although a C++ post, below is some C code

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

void form_text_number(char *buf, int significant_digits, int min_expo, int max_expo) {
  unsigned i = 0;
  buf[i++] = (char) (rand() % 9 + '1');
  buf[i++] = '.';
  for (int sd = 1; sd < significant_digits; sd++) {
    buf[i++] = (char) (rand() % 10 + '0');
  }
  sprintf(buf + i, "e%+03d", rand() % (max_expo - min_expo + 1) + min_expo);
}

bool round_trip_text_double_text(const char *s, int significant_digits) {
  double d = atof(s);
  char buf[significant_digits + 10];
  sprintf(buf, "%.*e", significant_digits - 1, d);
  if (strcmp(s, buf)) {
    printf("Round trip failed \"%s\" %.*e \"%s\"\n", s, significant_digits - 1 + 3,d, buf);
    return false;
  }
  return true;
}

Test code

void test_sig(unsigned n, int significant_digits, int min_expo, int max_expo) {
  printf("Sig digits %2d: ", significant_digits);
  while (n-- > 0) {
    char buf[100];
    form_text_number(buf, significant_digits, min_expo, max_expo);
    if (!round_trip_text_double_text(buf, significant_digits)) {
      return;
    }
  }
  printf("None Failed\n");
}

int main(void) {
  test_sig(10000, 16, -300, 300);
  test_sig(10000, 16, -1, -1);
  test_sig(1000000, 15, -300, 300);
  test_sig(1000000, 15, -1, -1);
  return 0;
}

Output

Sig digits 16: Round trip failed "8.995597974696435e+110" 8.995597974696434373e+110 "8.995597974696434e+110"
Sig digits 16: Round trip failed "6.654469376627144e-01" 6.654469376627144550e-01 "6.654469376627145e-01"
Sig digits 15: None Failed
Sig digits 15: None Failed

Note: When the double was printed to 3 extra digits for many failed strings, those 3 digits were in the range 445 to 555.

0
votes

There are 52 explicit bits for significand (or mantissa) and one implicit extra bit according to IEEE 754. So all integers of 53 bits are represented precisely as double. Integers of 54 or more bits will lose low bits, so they will not represented precisely if that bits are non-zero. So least integer that not represented precisely as double is 1ULL << 53 + 1

Program that shows it:

#include <iostream>
#include <cstdint>

int main(int, char**) {
    std::uint64_t i = (1ULL << 53) + 1;
    double x = i;
    std::uint64_t j = x;
    std::cout << x << " " << i << " " << j << std::endl;
    return 0;
}