#c#
#c#
Вопрос:
Это просто для академических целей.
Я заметил, что для целых литералов мы можем объявить, до 18446744073709551615
которого равно 2^64-1
или ulong.MaxValue
. Определение большего, чем это значение, приводит к ошибке времени компиляции.
А для литералов с плавающей запятой мы можем объявлять их с целой частью до 999...999
( 9
повторяется 308 раз). Объявление целочисленной части с большим количеством цифр снова приводит к ошибке времени компиляции. Меня интересует то, что компилятор, похоже, позволяет нам указывать в дробной части неограниченное количество цифр. Практически неограниченное количество цифр для дробной части не имеет смысла.
Вопросы:
-
Существует ли константа, представляющая максимальное количество цифр, внутренне определенных компилятором C # для дробной части числа с плавающей запятой?
-
Если такая константа существует, почему компилятор 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
Комментарии:
1. Я не могу сказать наверняка, но если бы мне пришлось догадываться, это могло бы быть в этом пикантном кусочке из спецификации : «Если указанный литерал не может быть представлен в указанном типе, тогда возникает ошибка времени компиляции. Значение реального литерала типа
float
ordouble
определяется с помощью режима IEEE «округление до ближайшего». » Так что, возможно, в этом случае это сумасшедшее длинное значение может быть «представлено» как число с плавающей запятой (поскольку почти все они являются приближением), или это может быть «IEEE round to nearest mode», который может позволить это.2. Вам не нужно так много цифр, чтобы увидеть поведение,
0.9999999999999995
представлено как0.999999999999999
, где0.99999999999999951
представлено как1
.3. Для десятичных разрядов оно, по-видимому, округляется до ближайшего представимого значения (если оно находится между
Double.MinValue
иDouble.MaxValue
). Такимconst double d = 0.999...
образом (повторяется до 2000 цифр) компилируется в IL-коде как1
. То естьconst double @d = 1;
компилируется в точно такой же IL-код , посколькуconst double @double = 0.999...;
это может «не иметь смысла», но поскольку вполне вероятно, что любая введенная вами фракция не будет существовать как точное значение, она, вероятно, использует те же правила аппроксимации и является представимой , тогда как вне диапазонов Min / Max не представимы (таким образом, ошибка).
Ответ №1:
- Да, но это не десятичные цифры
- Спецификация дробных частей за пределами возможности их точного представления проста, когда спецификация десятичная, а представление двоичное.
0.3
уже требует аппроксимации.
Ответ №2:
В большинстве случаев число с плавающей запятой в любом случае будет приближением к желаемому реальному значению (если только оно не является одним из значений, которые могут быть представлены точно). Кроме того, приближение четко определено: просто округлите до ближайшего представимого значения. С другой стороны, не существует полезного способа округления целого числа (или целой части действительного числа) до ближайшего представимого значения. Что значит, например, округлить 2 ^ 100 до 2 ^ 64-1?
Ответ №3:
Я не знаю о каких-либо ограничениях на количество десятичных знаков, разрешенных в литерале с плавающей запятой, хотя проверить, действительно ли существует такой предел, должно быть относительно просто, хотя, если это так, это, вероятно, больше зависит от внутренних компонентов компилятора, чем от чего-либо конкретного для самих значений с плавающей запятой. Однако я думаю, что стоит подумать о том, имеет ли смысл вообще ограничивать количество десятичных знаков в литерале. Я думаю, что ключевым моментом здесь является разница между числами, которые не могут быть представлены, поскольку они находятся за пределами поддерживаемого диапазона двойного типа данных (которые подбираются компилятором), и числами, которые не могут быть представлены точно в пределах типа данных.
Действительно, существует много десятичных чисел, которые не могут быть представлены точно как двойные (например, 0,1), и все же компилятор молча принимает их, преобразуя их в ближайшее представимое значение, и было бы значительным неудобством, если бы это было не так. Почему поэтому литерал с избытком десятичных знаков должен обрабатываться по-другому?