C# Decimal.Epsilon

本文关键字:Epsilon Decimal | 更新日期: 2024-09-21 08:44:30

为什么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;

顺便说一下,有几个问题提供了关于Decimal数据类型的内部表示的信息:

  • c中的十进制误解
  • 什么';小数可以表示的第二个最小值是什么

这里有一个Epsilon有用的例子。

比方说,我有一个来自某个采样集的值的加权和和所取样本的权重(或计数)的和。现在我想计算加权平均值。但我知道权重之和(或计数)可能仍然为零。为了防止被零除,我可以做if... else...并检查零。或者我可以这样写:

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

这个代码在我看来更短。或者,我可以跳过+ T.Epsilon,改为使用初始化

T weighted_count = T.Epsilon;

当我知道实际权重的值永远不会接近Epsilon时,我可以这样做。

对于某些数据类型和用例,这可能会更快,因为它不涉及分支。据我所知,即使分支很短,处理器也不能同时使用两个分支进行计算。我可能知道,零以50%的速率随机出现:=)对于Decima l,速度方面可能并不重要,甚至在第一种情况下也不太有用。

我的代码可能是通用的(例如,生成的),我不想为小数编写单独的代码。因此,我们希望看到Decimal具有与其他实值类型类似的接口。

C# Decimal.Epsilon

与该定义相反,ε实际上是一个用于消除值的二进制和十进制表示之间转换的模糊性的概念。例如,十进制中的0.1没有简单的二进制表示,因此当您将双精度声明为0.1时,实际上是将该值设置为二进制的近似表示。如果你把这个二进制表示数加10次(数学上),你得到的数字大约是1.0,但不完全是。ε会让你伪造数学,并说0.1的近似表示加在它本身上可以被认为等同于0.2的近似表示。

对于已经是十进制表示的十进制值类型,不需要由表示的性质引起的这种近似。这就是为什么每当你需要处理实际数字和本身不是近似值的数字(即货币而不是质量)时,正确的浮点类型是十进制,而不是双精度。

我能计算出的最小小数是:

public static decimal DecimalEpsilon = (decimal) (1 / Math.Pow(10, 28));

这来自于在C#交互窗口中运行以下内容:

for (int power = 0; power <= 50; power++) { Console.WriteLine($"1 / 10^{power} = {((decimal)(1 / (Math.Pow(10, power))))}"); }

其输出如下:

1 / 10^27 = 0.000000000000000000000000001
1 / 10^28 = 0.0000000000000000000000000001
1 / 10^29 = 0
1 / 10^30 = 0

如果我们只考虑96位尾数,Decimal类型可以被认为具有等于由96个设置位构造的BigInteger的倒数的ε。这个数字显然太小了,无法用当前的内在值类型来表示。

换句话说,我们需要一个"BigReal"值来表示这么小的分数。

坦率地说,这只是ε的"粒度"。然后,我们需要知道指数(GetBits()中最高Int32的16-23位),才能得到GIVEN十进制值的"真实"ε。

显然,"epsilon"对于Decimal的含义是可变的。您可以将粒度epsilon与指数一起使用,并为GIVEN小数得出特定的epsilon。

但考虑一下以下相当有问题的情况:

[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, " "));
}

12月1日:00000000000000000000000000000000000000000000000000000000000000000000000000001010

12月2日;0000000000000010 00000000000000000000000000000000000000000000000000000000000000000000000001100100

尽管两个解析的值看起来相等,但它们的表示并不相同!

这个故事的寓意是……在认为自己理解十进制之前,要非常小心,要彻底理解十进制!!!

提示:

如果您想要Decimal的epsilon(理论上),请创建一个UNION ([StructLayout[LayoutKind.Explicit]),它结合Decimal(128位)、BigInteger(96位)和Exponent(8位)。Epsilon的getter将根据粒度Epsilon和指数返回正确的BigReal值;当然,假设BigReal定义的存在(我已经听了很长一段时间了,它即将到来)。

顺便说一句,粒度epsilon应该是一个常量或静态字段。。。

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

家庭作业:BigInteger的最后一个字节应该是0xFF还是0x7F(或者其他什么)?

附言:如果所有这些听起来比你希望的要复杂。。。考虑到comp科学的回报相当不错。/-)