Подтвердить что ты не робот

С# Decimal.Epsilon

Почему тип данных Decimal имеет поле Epsilon?

Из руководства диапазон значений Decimal составляет ± 1.0 × 10e-28 до ± 7.9 × 10e28.

Описание Double.Epsilon:

Представляет наименьшее положительное значение Double больше нуля

Итак, похоже, Decimal имеет такое (нетривиальное) значение. Но почему он не легко доступен?

Я понимаю, что +1.0 × 10e-28 - это наименьшее положительное десятичное значение, большее нуля:

decimal Decimal_Epsilon = new decimal(1, 0, 0, false, 28); //1e-28m;

Кстати, есть несколько вопросов, которые дают информацию о внутреннем представлении типа десятичного типа:

Вот пример, когда Epsilon был бы полезен.

Допустим, что у меня есть взвешенная сумма значений из некоторого набора выборки и суммы весов (или количества) взятых проб. Теперь я хочу вычислить средневзвешенное значение. Но я знаю, что сумма весов (или счет) может быть равна нулю. Чтобы предотвратить деление на ноль, я мог бы сделать if... else... и проверить нуль. Или я мог бы написать вот так:

T weighted_mean = weighted_sum / (weighted_count + T.Epsilon)

Этот код короче в моих глазах. Или, альтернативно, я могу пропустить + T.Epsilon и вместо этого инициализировать с помощью:

T weighted_count = T.Epsilon;

Я могу это сделать, когда знаю, что значения реальных весов никогда не близки к Epsilon.

И для некоторых типов данных и прецедентов это, возможно, даже быстрее, поскольку в нем нет ветвей. Насколько я понимаю, процессоры не могут принимать обе ветки для вычисления, даже если ветки коротки. И я могу знать, что нули происходят случайным образом со скоростью 50%: =) Для Decima l, аспект скорости, вероятно, не важен или даже положительно полезен в первом случае.

Мой код может быть общим (например, сгенерирован), и я не хочу писать отдельный код для десятичных знаков. Поэтому хотелось бы видеть, что Decimal имеет аналогичный интерфейс, как и другие вещественные типы.

4b9b3361

Ответ 1

Вопреки этому определению, epsilon на самом деле является понятием, используемым для устранения двусмысленности преобразования между двоичным и десятичным представлениями значений. Например, 0,1 в десятичной форме не имеет простого двоичного представления, поэтому, когда вы объявляете double как 0,1, он фактически устанавливает это значение в приблизительное представление в двоичном формате. Если вы добавите это число двоичного представления самому себе 10 раз (математически), вы получите число, равное 1,0, но не точно. Эпсилон позволит вам вычитать математику и сказать, что приближенное представление 0,1, добавленное к себе, можно считать эквивалентным приблизительному представлению 0.2.

Это приближение, вызванное характером представлений, не требуется для типа десятичного значения, которое уже является десятичным представлением. Вот почему каждый раз, когда вам нужно иметь дело с действительными числами и цифрами, которые сами по себе не являются приближениями (т.е. Деньгами, а не массовыми), правильный тип с плавающей запятой для использования является десятичным и не двойным.

Ответ 2

Если мы просто подумаем о 96-битной мантиссе, тип Decimal можно представить как имеющую эпсилон, равный обратному из BigInteger, построенного с 96 установленными битами. Это, очевидно, слишком малое число, чтобы представлять его с текущими внутренними типами значений.

Другими словами, для представления такой малой доли нам понадобится значение "BigReal".

И, откровенно говоря, это просто "гранулярность" эпсилона. Затем нам нужно было бы знать экспонента (бит 16-23 наивысшего Int32 из GetBits()), чтобы получить "реальный" эпсилон для десятичного значения GIVEN.

Очевидно, значение "epsilon" для Decimal является переменной. Вы можете использовать эпсилон гранулярности с показателем экспоненты и создать специальный эпсилон для десятичного десятичного числа.

Но рассмотрим следующую довольно проблематичную ситуацию:

[TestMethod]
public void RealEpsilonTest()
{
    var dec1 = Decimal.Parse("1.0");
    var dec2 = Decimal.Parse("1.00");
    Console.WriteLine(BitPrinter.Print(dec1, " "));
    Console.WriteLine(BitPrinter.Print(dec2, " "));
}

DEC1: 00000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00001010

DEC2; 00000000 00000010 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100

Несмотря на то, что два проанализированных значения кажутся равными, их представление не то же самое!

Мораль этой истории... будьте очень осторожны, чтобы вы полностью поняли, что Десятичный, прежде чем ДУМАТЬ, что вы это понимаете!

СОВЕТ:

Если вы хотите использовать epsilon для Decimal (теоретически), создайте UNION ([StructLayout[LayoutKind.Explicit]), объединяющий десятичные (128 бит) и BigInteger (96 бит) и Exponent (8 бит). Геттер для Epsilon вернет правильное значение BigReal, основанное на эпсилоне и экспоненте гранулярности; предполагая, конечно, существование определения BigReal (которое я слышал в течение довольно долгого времени).

Эффект гранулярности, кстати, будет постоянным или статическим полем...

static grain = new BigReal(1 / new BitInteger(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF });

HOMEWORK: Должен ли последний байт для BigInteger быть 0xFF или 0x7F (или вообще что-то еще)?

PS: Если все это звучит намного сложнее, чем вы надеялись,... считайте, что comp-теория платит достаточно хорошо./-)