1

If you evaluate

(Math.PI).ToString("G51")

the result is

"3,141592653589793115997963468544185161590576171875"

Math.Pi is correct up to the 15th decimal which is in accordance with the precision of a double. "G17" is used for round tripping, so I expected the ToString to stop after "G17", but instead it continued to produce numbers up to "G51".

Q Since the digits after 15th decimal are beyond the precision afforded by the 53 bits of mantissa, how are the remaining digits calculated?

phuclv
  • 37,963
  • 15
  • 156
  • 475
participant
  • 2,923
  • 2
  • 23
  • 40
  • 1
    See https://0.30000000000000004.com/ – Richard Nov 04 '22 at 10:54
  • Please write the result of 1/3 in base 10. Hint: you can't. You can only write combinations of powers of 10 (e.g. 10^-1 (aka 0.1), 2*10^-1 (0.2), 10^-2 (0.01), etc. So 0.23 is possible, but 1/3 isn't 0.333, and not even 0.334, at least not exactly. Likewise with binary you can only use combinations of powers of 2. – ProgrammingLlama Nov 04 '22 at 12:10
  • @ProgrammingLlama, @Richard That's not my point. A double has a 53 bit mantissa which corresponds to 15 decimals. So where does the noise after the 17th decimal come from? Does `ToString("G51")` make them up? – participant Nov 04 '22 at 13:10
  • 1
    I have voted to reopen. There are many answers in the proposed dup but I don't think any of them directly address this question. Note that the decimal digits appears to be simply the exact base 10 value of the approximated double – President James K. Polk Nov 04 '22 at 21:11

2 Answers2

1

A double has a 53 bit mantissa which corresponds to 15 decimals

A double has a 53 bit mantissa which corresponds to roughly 15 decimals.

Math.Pi is truncated at 53 bits in binary, which is not precisely equal to

3.1415926535897

but it's also not quite equal to

3.14159265358979

, etc.

Just like 1/3 is not quite 0.3, or 0.33, etc.

If you continue the decimal expansion long enough it will eventually terminate or start repeating, since Math.PI, unlike π is a rational number.

And as you can verify with a BigRational type

using System.Numerics;

var r = new BigRational(Math.PI);
BigRational dem;

BigRational.NumDen(r, out dem);
var num = r * dem;

Console.WriteLine(num);
Console.WriteLine("-----------------");
Console.WriteLine(dem);

it's

884279719003555
---------------
281474976710656

And since the denominator is a power of 2 (which it has to be since Math.PI has a binary mantissa and so a terminating representation in base-2). It also therefore has a terminating representation in base-10, which is exactly what was given by (Math.PI).ToString("G51"):

3.141592653589793115997963468544185161590576171875

as

Console.WriteLine(r == BigRational.Parse("3.141592653589793115997963468544185161590576171875"));

outputs

True
David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • 1
    the exact value of 884279719003555/281474976710656 is [3.141592653589793115997963468544185161590576171875](https://www.wolframalpha.com/input?i=884279719003555%2F281474976710656) – phuclv Nov 05 '22 at 14:22
0

The .ToString("G51") method uses 'school math'. First it takes the integer part, then it multiplies the Remainder by 10, use the integer part of that calculation and multiplies the Remainder part by 10.

It will keep doing that as long as there is a remainder and up to 51 decimal digits.

Since double has an exponent, it will keep having a remainder with about 18 decimal digits, which produces the next digit in ToString method.

However, as you have seen, the Remainder part is not accurate but simply a result of a math function.

Poul Bak
  • 10,450
  • 5
  • 32
  • 57