3

How long double fits so many characters in just 12 bytes?

I made an example, a C ++ factorial when entering a large number, 1754 for example it calculates with a number that apparently would not fit a long double type.

#include <iostream>
#include <string.h>

using namespace std;
int main()
{
    unsigned int n;
    long double fatorial = 1;
    cout << "Enter number: ";
    cin >> n;
    for(int i = 1; i <=n; ++i)
    {
        fatorial *= i;
    }

    string s = to_string(fatorial);
    cout << "Factorial of " << n << " = " <<fatorial << " = " << s;
    return 0;
}

Important note: GCC Compiler on Windows, by visual Studio long double behaves like a double

The problem is how is it stored or the to_string function? enter image description here

Dorathoto
  • 201
  • 7
  • 17
  • Note the result is not entirely accurate, since the factorial of any integer which is at least 5 must end in a zero. – aschepler Jul 23 '19 at 20:19
  • Maybe a duplicate? https://stackoverflow.com/q/2706851/1896169 – Justin Jul 23 '19 at 20:19
  • `long double` seems the wrong data type for this.. – Jesper Juhl Jul 23 '19 at 20:19
  • Also related: https://stackoverflow.com/q/45073141/1896169 – Justin Jul 23 '19 at 20:20
  • 2
    It's very easy to write a large number with very few digits. 2 to the power of 1000, is a very big number with roughly 300 digits but I wrote it using only five digits. Well that's exactly how long double does it. – john Jul 23 '19 at 20:20
  • 1
    Possible duplicate of [How are floating point numbers are stored in memory?](https://stackoverflow.com/questions/7644699/how-are-floating-point-numbers-are-stored-in-memory) – Justin Jul 23 '19 at 20:21
  • @jxh No, it's specified to do this. [`std::to_string`](https://en.cppreference.com/w/cpp/string/basic_string/to_string) acts like [`sprintf(buf, "%Lf", d)`](https://en.cppreference.com/w/cpp/io/c/fprintf), which has a default precision of 6 digits after the decimal place. – Justin Jul 23 '19 at 20:25
  • Interesting: I duplicated the result online here: http://cpp.sh/5msos – drescherjm Jul 23 '19 at 20:27
  • Not sure why the link broke. Limit on number of executions?? – drescherjm Jul 23 '19 at 20:34

3 Answers3

3

It doesn't fit that many characters. Rather, to_string produces that many characters from the data.

Here is a toy program:

std::string my_to_string( bool b ) {
  if (b)
    return "This is a string that never ends, it goes on and on my friend, some people started typing it not knowing what it was, and now they still are typing it it just because this is the string that never ends, it goes on and on my friend, some people started typing it not knowing what it was, and now they still are typing it just because...";
  else
    return "no it isn't, I can see the end right ^ there";
}

bool stores exactly 1 bit of data. But the string it produces from calling my_to_string can be as long as you want.

double's to_string is like that. It generates far more characters than there is "information" in the double.

This is because it is encoded as a base 10 number on output. Inside the double, it is encoded as a combination of an unsigned number, a sign bit, and an exponential part.

The "value" is then roughly "1+number/2^constant", times +/- one for the sign, times "2^exponential part".

There are only a certain number of "bits of precision" in base 2; if you printed it in base 2 (or hex, or any power-of-2 base) the double would have a few non-zero digits, then a pile of 0s afterwards (or, if small, it would have 0.0000...000 then a handful of non-zero digits).

But when converted to base 10 there isn't a pile of zero digits in it.

Take 0b10000000 -- aka 2^8. This is 256 in base 10 -- it has no trailing 0s at all!

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
3

std::to_string(factorial) will return a string containing the same result as std::sprintf(buf, "%Lf", value).

In turn, %Lf prints the entire integer part of a long double, a period and 6 decimal digits of the fractional part.

Since factorial is a very large number, you end up with a very long string.

However, note that this has nothing to do with long double. A simpler example with e.g. double is:

#include <iostream>
#include <string>

int main()
{
    std::cout << std::to_string(1e300) << '\n';
    return 0;
}

This will print:

10000000000000000525047602 [...300 decimal digits...] 540160.000000

The decimal digits are not exactly zero because the number is not exactly 1e300 but the closest to it that can be represented in the floating-point type.

Acorn
  • 24,970
  • 5
  • 40
  • 69
  • very good this example, simple and to the point, but is this feature because of the correct to_string? – Dorathoto Jul 26 '19 at 13:15
  • @Dorathoto To what feature in particular are you referring to? – Acorn Jul 26 '19 at 16:22
  • that it is not only zeros in 1e300 (should have 300 zeros) – Dorathoto Jul 26 '19 at 17:06
  • @Dorathoto No, that is a feature of the language. When you use a literal, the compiler will use (one of) the closest values that can be represented in the given type, because you cannot store `1e300` in `double` exactly. So you end up with another value (which has the non-zero digits). Then `std::to_string` is called with that value. – Acorn Jul 26 '19 at 17:09
  • @jxh No, it cannot be stored exactly. There are algorithms that approximate a decimal representation, but that is not the issue here. – Acorn Jul 30 '19 at 01:46
  • You're right. `0b1010` raised to the 300th power would require too many bits. Thanks. – jxh Jul 30 '19 at 18:18
1

This is because floating point numbers only store an approximation of the actual value. If you look at the actual exact value of 1754! you'll see that your result becomes completely different after the first ~18 digits. The digits after that are just the result of writing (a multiple of) a large power of two in decimal.

eesiraed
  • 4,626
  • 4
  • 16
  • 34