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

It is just for academic purpose.

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

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

Questions:

  • Is there a constant representing the maximum number of digits internally defined by the C # compiler for the fractional part of a floating point number?

  • If such a constant exists, why does the C # compiler not compile a compilation error when users specify fractional parts outside their 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 
+6
source share
3 answers
  • Yes, but they are not decimal digits
  • The specification of fractional parts that go beyond the ability to represent them accurately is simple when the specification is decimal and the representation is binary. 0.3 already requires approximation.
+2
source

In most cases, a floating point number will in any case be an approximation of the desired real value (unless it is one of the values โ€‹โ€‹that can be represented exactly). In addition, the approximation is well defined: it simply rounds 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 round 2 ^ 100 to 2 ^ 64-1 mean, for example?

+2
source

I do not know a single limit in the number of decimal places allowed in a floating point literal, although it should be relatively simple to check whether such a limit really exists, although if so, it probably depends more on the internal components of the compiler than anything specific for floating point values โ€‹โ€‹themselves. However, I think itโ€™s worth considering whether it makes sense to limit the number of decimal places in the literal. I think the key point here is the difference between numbers that cannot be represented, since they are outside the supported range of the double data type (which are selected by the compiler) and numbers that cannot be represented exactly within the data type.

In fact, there are many decimal numbers that cannot be represented exactly as doubles (e.g. 0.1), and yet the compiler silently accepts them, converting them to the nearest representable value, and this would be a significant inconvenience if this did not happen. Why therefore should a literal with an excess of decimals be processed differently?

+1
source

Source: https://habr.com/ru/post/971361/


All Articles