50

为什么Decimal数据类型没有Epsilon字段?

根据手册,取值范围decimal为 ±1.0 × 10e−28 到 ±7.9 × 10e28。

的描述Double.Epsilon

Double表示大于零的最小正值

所以看起来,Decimal也有这样一个(非平凡的)价值。但是为什么它不容易访问呢?

我确实明白 +1.0 × 10e−28 正是大于零的最小正 Decimal 值:

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

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

Epsilon这是一个有用的例子。

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

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

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

T weighted_count = T.Epsilon;

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

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

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

4

3 回答 3

14

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

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

于 2012-08-02T16:46:08.210 回答
2

如果我们只考虑 96 位尾数,则可以认为 Decimal 类型的 epsilon 等于由 96 位设置的 BigInteger 的倒数。这显然是一个太小的数字,无法用当前的内在价值类型来表示。

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

坦率地说,这只是 epsilon 的“粒度”。然后,我们需要知道指数(来自 GetBits() 的最高 Int32 的第 16-23 位)以得出给定十进制值的“真实” epsilon。

显然,“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, " "));
}

DEC1: 00000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000000 00000000000000000

十二月2; 00000000 00000010 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000000000000000000000000

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

这个故事的寓意是......在认为你理解它之前要非常小心,你彻底理解了 Decimal !!!

暗示:

如果您想要 Decimal 的 epsilon(理论上),请创建一个([StructLayout[LayoutKind.Explicit])结合 Decimal(128 位)和 BigInteger(96 位)和 Exponent(8 位)的 UNION。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 的最后一个字节应该是0xFF0x7F(或其他东西)?

PS:如果所有这些听起来都比您希望的要复杂得多,那么……考虑一下计算机科学的回报相当不错。/-)

于 2014-09-18T03:06:12.140 回答
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
于 2018-10-04T12:33:41.253 回答