简体   繁体   中英

C# compiler does not limit the number of digits of fractional part of a floating-point literal

This is just for academic purpose.

I noticed that for integral literals, we can declare up to 18446744073709551615 which is 2^64-1 or ulong.MaxValue . Defining greater than this value produces a compile-time error.

And for floating-point literals, we can declare them with integral part up to 999...999 ( 9 is repeated 308 times). Declaring the integral part with more digits again produces a compile-time error. One thing that interests me is that the compiler seems to allow us specify the fractional part unlimited number of digits. Practically, unlimited number of digits for the fractional part does not make sense.

Questions:

  1. Is there a constant representing the max number of digits internally defined by C# compiler for the fractional part of a floating-point number?

  2. If such a constant exists, why does not C# compiler throw compile-time error when users specify the fractional parts beyond its limit?

Minimal Working Example 1

namespace FloatingPoint
{
    class Program
    {
        static void Main(string[] args)
        {
            const ulong @ulong = 18446744073709551615;
            const double @double = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999.9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;

        }
    }
}

Minimal Working Example 2

using System;

namespace FloatingPoint
{
    class Program
    {
        static void Main(string[] args)
        {

            const double x01 = 0.9;
            const double x02 = 0.99;
            const double x03 = 0.999;
            const double x04 = 0.9999;

            const double x05 = 0.99999;
            const double x06 = 0.999999;
            const double x07 = 0.9999999;
            const double x08 = 0.99999999;

            const double x09 = 0.999999999;
            const double x10 = 0.9999999999;
            const double x11 = 0.99999999999;
            const double x12 = 0.999999999999;

            const double x13 = 0.9999999999999;
            const double x14 = 0.99999999999999;
            const double x15 = 0.999999999999999;
            const double x16 = 0.9999999999999999;

            const double x17 = 0.99999999999999999;
            const double x18 = 0.999999999999999999;
            const double x19 = 0.9999999999999999999;
            const double x20 = 0.99999999999999999999;

            Console.WriteLine(x01);
            Console.WriteLine(x02);
            Console.WriteLine(x03);
            Console.WriteLine(x04);
            Console.WriteLine(x05);
            Console.WriteLine(x06);
            Console.WriteLine(x07);
            Console.WriteLine(x08);
            Console.WriteLine(x09);
            Console.WriteLine(x10);
            Console.WriteLine(x11);
            Console.WriteLine(x12);
            Console.WriteLine(x13);
            Console.WriteLine(x14);
            Console.WriteLine(x15);
            Console.WriteLine(x16);
            Console.WriteLine(x17);
            Console.WriteLine(x18);
            Console.WriteLine(x19);
            Console.WriteLine(x20);

        }
    }
}

/* output:

0.9
0.99
0.999
0.9999
0.99999
0.999999
0.9999999
0.99999999
0.999999999
0.9999999999
0.99999999999
0.999999999999
0.9999999999999
0.99999999999999
0.999999999999999
1
1
1
1
1
*/

IL:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       302 (0x12e)
  .maxstack  1
  IL_0000:  nop
  IL_0001:  ldc.r8     0.90000000000000002
  IL_000a:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_000f:  nop
  IL_0010:  ldc.r8     0.98999999999999999
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_001e:  nop
  IL_001f:  ldc.r8     0.999
  IL_0028:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_002d:  nop
  IL_002e:  ldc.r8     0.99990000000000001
  IL_0037:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_003c:  nop
  IL_003d:  ldc.r8     0.99999000000000005
  IL_0046:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_004b:  nop
  IL_004c:  ldc.r8     0.99999899999999997
  IL_0055:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_005a:  nop
  IL_005b:  ldc.r8     0.99999990000000005
  IL_0064:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_0069:  nop
  IL_006a:  ldc.r8     0.99999998999999995
  IL_0073:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_0078:  nop
  IL_0079:  ldc.r8     0.99999999900000003
  IL_0082:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_0087:  nop
  IL_0088:  ldc.r8     0.99999999989999999
  IL_0091:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_0096:  nop
  IL_0097:  ldc.r8     0.99999999999
  IL_00a0:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00a5:  nop
  IL_00a6:  ldc.r8     0.99999999999900002
  IL_00af:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00b4:  nop
  IL_00b5:  ldc.r8     0.99999999999989997
  IL_00be:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00c3:  nop
  IL_00c4:  ldc.r8     0.99999999999999001
  IL_00cd:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00d2:  nop
  IL_00d3:  ldc.r8     0.999999999999999
  IL_00dc:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00e1:  nop
  IL_00e2:  ldc.r8     0.99999999999999989
  IL_00eb:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00f0:  nop
  IL_00f1:  ldc.r8     1.
  IL_00fa:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_00ff:  nop
  IL_0100:  ldc.r8     1.
  IL_0109:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_010e:  nop
  IL_010f:  ldc.r8     1.
  IL_0118:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_011d:  nop
  IL_011e:  ldc.r8     1.
  IL_0127:  call       void [mscorlib]System.Console::WriteLine(float64)
  IL_012c:  nop
  IL_012d:  ret
} // end of method Program::Main
  1. Yes, but they aren't decimal digits
  2. Specification of fractional parts beyond the ability to exactly represent them is easy, when the specification is decimal and the representation is binary. 0.3 already requires approximation.

In most cases, a floating point number will be an approximation of the desired real value anyway (unless it happens to be one of the values that can be represented exactly). Furthermore, the approximation is well-defined: simply round to the nearest representable value. On the other hand, there is no useful way to round an integer (or the integer part of a real number) to the nearest representable value. What does it mean to round 2^100 to 2^64-1, for example?

I'm not aware of any limit in the number of decimals permitted in a floating point literal though it should be relatively simple to test whether such a limit does indeed exist, though if it does, it likely depends more on compiler internals than anything specific to floating point values themselves anyway. However I think it's worth thinking about whether it makes sense to limit the number of decimals in a literal at all. I think the key point here is the difference between numbers that cannot be represented because they are outside the supported range of the double data type (which are picked up by the compiler), and numbers that cannot be represented exactly within the data type.

There are indeed many decimal numbers that cannot be represented exactly as doubles (eg 0.1), and yet the compiler silently accepts them, converting them to the closest representable value, and it would be a significant inconvenience if it did not. Why therefore should a literal with an excess of decimals be treated any differently?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM