提问人:pmcilreavy 提问时间:10/8/2023 最后编辑:pmcilreavy 更新时间:10/9/2023 访问量:105
C# IEEE754舍入
C# IEEE754 Rounding
问:
请考虑以下 C# 代码...
double x = Math.Round(72.6d, 2, MidpointRounding.ToZero);
double y = Math.Round(82.6d, 2, MidpointRounding.ToZero);
x
成为并成为.72.59
y
82.6
但是为什么?通过这个IEEE754转换器,两者的小数部分是相同的。那么他们为什么不给出相同的结果呢?
我可以通过执行以下操作来解决问题。但我更感兴趣的是知道为什么它似乎没有按预期工作。(double)Math.Round(Convert.ToDecimal(72.6d), 2, MidpointRounding.ToZero)
答:
TLDR:浮点类型,在进行数学运算时有精度损失; 四舍五入时乘以 10^digit,这会导致精度损失; 不舍入,仅截断值。因此,当四舍五入到小数点后 2 位时,该方法将计算(由于乘法时精度的双倍损失),然后被截断为,因为您正在使用,然后除以 100 返回。Single
Double
Round
MidpointRounding.ToZero
Round
72.6d*100=7259.9999999999991;
7259
MidpointRounding.ToZero
72.59
详:它必须具有浮点数的精度和舍入期间的二进制表示形式与提供的文字值不同。正如您已经发现的那样,您可以改用十进制,这没有相同的舍入问题。
以下是文档中的原因部分:
为了确定舍入运算是否涉及中点 值,则 Round 方法将要舍入的原始值乘以 10n,其中 n 是返回中所需的小数位数 值,然后确定剩余的小数部分是否 该值大于或等于 .5。这是一个细微的变化 在平等测试中,正如“平等测试”中所讨论的 双重引用主题的部分,测试是否相等 由于浮点,浮点值存在问题 格式在二进制表示和精度方面的问题。这意味着 略小于 .5 的数字的任何小数部分 (由于精度损失)不会向上舍入。
以下是可能导致它的原因:
四舍五入中点值的精度问题最有可能 出现以下情况:
当小数值不能精确地表示时 浮点类型的二进制格式。
当要舍入的值由一个或多个计算得出时 浮点运算。
当要舍入的值是 Single 而不是 Double 时,或者 十进制。有关详细信息,请参阅下一节舍入和 单精度浮点值。
编辑:这是 72.59 的运行时值示例。在舍入过程中,它被乘以 100,然后用 截断。使用 ToZero。由于 72.59 * 100 存在浮点舍入精度问题,因此它最终为 7259.99999999999991,然后被截断为 7259 并在最后再次除以 100 得到 72.59。
我检查了Math.Round的源代码。以下是 Double MidpointRounding.ToZero 舍入的相关部分,注释中包含值。
value = 72.6; //value passed in
double num = roundPower10Double[digits]; //10^2 = 100
value *= num; //72.6d*100=7259.9999999999991
case MidpointRounding.ToZero:
value = Truncate(value); //7259
value /= num; //7259 / 100 = 72.59;
现在 82.6 也是如此。当 * 100 时,它没有相同的精度问题。
value = 82.6; //value passed in
double num = roundPower10Double[digits]; //10^2 = 100
value *= num; //82.6d*100=8260
case MidpointRounding.ToZero:
value = Truncate(value); //8260
value /= num; //8260 / 100 = 82.6;
示例代码显示:
static void Main(string[] args)
{
double xl = 72.6d;
double yl = 82.6d;
Console.WriteLine("Values * 100d:");
Console.WriteLine(xl * 100d);
Console.WriteLine(yl * 100d);
Console.WriteLine("Math.Round:");
double x = Math.Round(xl, 2, MidpointRounding.ToZero);
double y = Math.Round(yl, 2, MidpointRounding.ToZero);
Console.WriteLine(x);
Console.WriteLine(y);
}
调试输出:
另请查看我提供的链接中的“Round(Double, Int32, MidpointRounding) / Note to Callers”部分,该部分提供了类似的情况,其中 2.135 有问题,但 3.135 没有:
调用方说明 由于以下原因可能导致精度损失 将十进制值表示为浮点数或执行 对浮点值进行算术运算,在某些情况下 Round(Double, Int32, MidpointRounding) 方法可能不会显示为舍入 mode 参数指定的中点值。这是 在以下示例中进行了说明,其中 2.135 四舍五入为 2.13 而不是 2.14。发生这种情况是因为该方法在内部相乘 值乘以 10位,在本例中为乘法运算 遭受精度损失。
编辑2: 此外,无论中点如何,MidpointRounding.ToZero 将始终截断结果。请参阅此处的示例:https://learn.microsoft.com/en-us/dotnet/api/system.midpointrounding?view=net-7.0#examples
result = Math.Round(3.47m, 1, MidpointRounding.ToZero);
//result = 3.4
这意味着当我们得到 72.6 * 100 = 7259.999999999999991 时,由于算术运算期间的精度损失,那么由于您使用的是 MidpointRounding.ToZero,因此小数 .9 不会四舍五入到 7260,而是被截断并保留为 7259,然后返回 72.59 而不是 72.6。
评论
Double
Round
Round
根据 Microsoft 文档,不执行“中点舍入”。Microsoft似乎在这里搞砸了界面:MidpointRounding.ToZero
MidpointRounding
最初是作为枚举创建的,用于在四舍五入到最接近且平局与平局远离零 () 之间进行选择。ToEven
AwayFromZero
- 后来又增加了额外的舍入方法:向负无穷大舍入()、向正无穷大舍入()和向零()舍入。请注意,这些不会四舍五入到最接近的方向,而是朝向其中一个方向;他们向其中一个方向四舍五入。例如,使用 、 3.1、3.2、3.5、3.6 和 3.8 都四舍五入为 3。
Math.Round
ToNegativeInfinity
ToPositiveInfinity
ToZero
ToZero
- Microsoft 没有使用新名称(如 )创建新枚举,而是将这些新值添加为 中的成员。
RoundingMethod
MidpointRounding
结果是,所有五种舍入方法都具有表单名称,尽管其中只有两种是与中点相关的舍入到最接近的方法。MidpointRounding.Name
(我们还看到使用术语不敏感,而不是 3.4 四舍五入到 3,即接近零。它没有四舍五入到零。IEEE 754 使用“toward”表示这些舍入方法。To
Toward
除此之外,本文档告诉我们通过乘以 10n 并四舍五入到整数来操作,而不是正确确定原始数字的四舍五入,我们可以看到会发生什么:Math.Round
- 72.6 转换为 ,产生最接近的可表示值 72.5999999999999994315658113919198513031005859375。乘以 100,得到 7259.9999999999990905052982270717620849609375。在朝向零的方向上四舍五入为整数,得到 7259。除以 100,生成 72.5900000000000003410605131648480892181396484375,如果使用的位数少于 17 位,则打印为 72.59。
double
- 82.6 转换为 ,生成 82.599999999999994315658113919198513031005859375。乘以 100,得到 8260。这种情况与前一种情况之间的区别是由于实数相对于可表示数字的位置的偶然性。然后将 8260 除以 100,生成 82.599999999999994315658113919198513031005859375,如果使用的位数少于 16 位,则打印为 82.60。
double
评论
MidpointRounding.ToEven
ToZero
9.858
9.85
ToEven
9.86
MidpointRounding.ToZero
MidpointRounding.ToZero
MidpointRounding.ToEven