【问题标题】:C# compiler does not limit the number of digits of fractional part of a floating-point literalC#编译器不限制浮点文字小数部分的位数
【发布时间】:2014-06-26 09:54:42
【问题描述】:

这只是为了学术目的。

我注意到对于整型文字,我们最多可以声明 184467440737095516152^64-1ulong.MaxValue。定义大于此值会产生编译时错误。

对于浮点字面量,我们可以用整数部分声明它们,直到 999...9999 重复 308 次)。再次用更多数字声明整数部分会产生编译时错误。我感兴趣的一件事是编译器似乎允许我们指定小数部分无限位数。实际上,小数部分的无限位数是没有意义的。

问题:

  1. 是否有一个常量表示 C# 编译器内部为浮点数的小数部分定义的最大位数?

  2. 如果存在这样的常量,为什么当用户指定超出其限制的小数部分时,C# 编译器不会抛出编译时错误?

最小工作示例 1

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

        }
    }
}

最小工作示例 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

【问题讨论】:

  • 我不能肯定地说,但如果我不得不猜测,它可能在specification 的这个花絮中:“如果指定的文字不能用指定的类型表示,那么发生编译时错误。floatdouble 类型的实数值是通过使用 IEEE“四舍五入”模式确定的。所以也许在这种情况下,这个疯狂的长值可以“表示”为一个浮点数(因为几乎所有这些都是一个近似值),或者它可能是“IEEE 舍入到最近的模式”,它可能允许这样做。跨度>
  • 您不需要 那么多 位来查看行为,0.9999999999999995 表示为 0.999999999999999,其中 0.99999999999999951 表示为 1
  • 对于小数位,它似乎被四舍五入到最接近的可表示值(只要它在Double.MinValueDouble.MaxValue 之间)。所以const double d = 0.999...(重复到 2000 位)在 IL 代码中被编译1。也就是说,const double @d = 1; 编译为与const double @double = 0.999...; 完全相同的IL 代码 这可能没有“意义”,但因为您输入的any 派系很可能不会作为一个精确值存在,它可能使用相同的近似规则并且是 representable 而在 Min/Max 范围之外是 not 可表示的(因此是一个错误)。

标签: c#


【解决方案1】:
  1. 是的,但它们不是十进制数字
  2. 当规范是十进制并且表示是二进制时,超出精确表示它们的能力的小数部分的规范很容易。 0.3 已经需要近似值。

【讨论】:

    【解决方案2】:

    在大多数情况下,浮点数无论如何都是所需实数值的近似值(除非它恰好是可以精确表示的值之一)。此外,近似值是明确定义的:只需四舍五入到最接近的可表示值。另一方面,没有有用的方法将整数(或实数的整数部分)四舍五入到最接近的可表示值。例如,将 2^100 舍入为 2^64-1 是什么意思?

    【讨论】:

      【解决方案3】:

      我不知道浮点文字中允许的小数位数有任何限制,尽管测试这种限制是否确实存在应该相对简单,但如果确实存在,它可能更多地取决于编译器内部无论如何,比任何特定于浮点值本身的东西都要好。但是,我认为值得考虑限制文字中的小数位数是否有意义。我认为这里的关键点是无法表示的数字之间的区别,因为它们超出了双数据类型(由编译器拾取)支持的 range 和无法表示的数字正好在数据类型中。

      确实有许多十进制数不能精确地表示为双精度数(例如 0.1),但编译器会默默地接受它们,将它们转换为最接近的可表示值,如果不这样做将非常不便。因此,为什么要对带有多余小数的文字进行不同的处理?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-01-08
        • 1970-01-01
        • 2022-01-23
        • 1970-01-01
        • 2021-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多