3

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?

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 2
    Many articles on this site, here is one that looks relevant: http://www.exploringbinary.com/17-digits-gets-you-there-once-youve-found-your-way/ – Richard Critten Nov 07 '17 at 10:56
  • 1
    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 Nov 07 '17 at 10:58
  • @PascalCuoq I meant when both are rounded to X significant digits. That's the way "significant digits" works I believe. Of course there will be always error on representing a decimal in FP math. – markzzz Nov 07 '17 at 11:01
  • 2
    You could iterate through all possible floats using http://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 Nov 07 '17 at 11:15
  • 1
    It took a while but I've detailed the most tractable (in my opinion) range of numbers that serve as counterexamples. – Bathsheba Nov 07 '17 at 11:44
  • @markzzz So if I understand correctly, you are looking for two numbers x and y the decimal approximations of which coincide to N digits, differ to (N+1) digits and that have the same `double` representation, in particular for N=15. For well-chosen x and y, it is possible to get N above 750, as discussed in this answer: https://stackoverflow.com/a/17245451/139746 – Pascal Cuoq Nov 07 '17 at 12:44
  • Note: This question asks about “double precision,” but the double-precision floating-point format is not specified by C++. The most common one in use is IEEE-754 64-bit binary. Also note that although that format is the most common one in use, complete adherence to IEEE-754 arithmetic is rare; most C++ implementations fail to conform in various ways. – Eric Postpischil Nov 07 '17 at 14:14
  • @Bathsheba: (a) You would have some trouble iterating through all possible double-precision values. There are quite a few. (b) The exceptions are not evenly distributed. The relative precision of binary floating-point varies as the significand progresses from 1 to 2, and the relative precision of decimal varies as the first digit progresses from 1 to 9. You will want to search slightly above powers of two that have decimal representations starting with a large digit, because this is where the binary precision is low (significand near 1) but the decimal precision is high (first digit high). – Eric Postpischil Nov 07 '17 at 14:23
  • 1
    @EricPostpischil: Indeed, although I think you'd reach a counter-example pretty quickly. I still maintain they are distributed regularly in exponential space. – Bathsheba Nov 07 '17 at 14:29

4 Answers4

4

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

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Oh stuff that. Don't you think the above is beautiful? ;-) – Bathsheba Nov 07 '17 at 11:48
  • Yes I think so! But fractional make things dope <3 – markzzz Nov 07 '17 at 11:49
  • The same for float. Looking for an algo heheh – markzzz Nov 07 '17 at 11:49
  • Ok `562949953421312.7`, and `562949953421312.8` suffer the same thing. Same idea; pick a power of 2, with a large first digit, and play around. Both become the dyadic rational `562949953421312.75`. – Bathsheba Nov 07 '17 at 12:11
  • To be precise, `9007199254740993` is the smallest unrepresentable integer for the double precision format. For the single precision format, the smallest unrepresentable integer is `2^24 + 1 = 16777217` – cmaster - reinstate monica Nov 07 '17 at 12:11
  • Sorry, I meant a "normalized" value. 0 – markzzz Nov 07 '17 at 14:00
  • @cmaster: By “the” double precision format, you likely mean IEEE-754 64-bit binary. There are others, so we should be precise about which one. – Eric Postpischil Nov 07 '17 at 14:12
  • Well, how do you add 1e-16? Its also rounded... http://coliru.stacked-crooked.com/a/985352e38b78eff0 So I never write reach exactly that `0.7500000000000006`.... – markzzz Nov 07 '17 at 14:28
  • I'm not suggesting you do that computationally. The fact remains that the two decimals I've written in the answer recover the same number. Note you could wack this out quite quickly in java with their BigDecimal class. – Bathsheba Nov 07 '17 at 14:31
  • Yes that true! But I'd like to "find myself" the right ones :) I'm stuck with this... can't elaborate a valid algo... – markzzz Nov 07 '17 at 14:32
  • For an easy life, do it in Java! Note that their `double` is an IEEE754. – Bathsheba Nov 07 '17 at 14:33
  • Lol. Not I'm with c++ for some reasons ;) (audio plugin). But that's just "learning" and testing fp math... – markzzz Nov 07 '17 at 14:34
  • I found a way ;) Let me elaborate http://coliru.stacked-crooked.com/a/21b90c44c32bc15d – markzzz Nov 07 '17 at 14:38
  • @markzzz: Yes that's quite nice. Use a long long though as the integral type. There's no harm, by the way, in answering your own question - sure you know that, and I'll upvote it. – Bathsheba Nov 07 '17 at 14:40
  • @Bathsheba: did, and quoted you, for the help :) What do you think? It seems to works... – markzzz Nov 07 '17 at 14:53
  • 1
    @markzzz: It looks pretty good. I upvoted as promised, but did change the type of `decimalPart`, and added a comment about the term. – Bathsheba Nov 07 '17 at 14:56
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/158431/discussion-between-markzzz-and-bathsheba). – markzzz Nov 07 '17 at 15:58
  • FWIW, Could have started at 0.5 and moved up or 1.0 and moved down. 0.75 works well too. – chux - Reinstate Monica Nov 07 '17 at 19:56
  • Am I on drugs or it seems that between 0.1000000 and 0.9999999, using **single precision (float)**, all values with 7 digits never collide? http://coliru.stacked-crooked.com/a/fcc0d93f4a0eb06c – markzzz Nov 08 '17 at 08:31
1

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;        
    }
}
markzzz
  • 47,390
  • 120
  • 299
  • 507
  • Beware that `decimalPart / 1e16` is a floating point division and is therefore a floating point type itself, since `1e16` is a `double` literal. – Bathsheba Nov 07 '17 at 14:54
  • Of course. But for the purpose it serves me to just add `1e-16` for every decimal. – markzzz Nov 07 '17 at 14:56
1

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.

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

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;
}
user2807083
  • 2,962
  • 4
  • 29
  • 37