C#中的float/double Math.Round

21
float ff = (float)31.15;

double dd = 31.15;

var frst = Math.Round(ff, 1, MidpointRounding.AwayFromZero);

var drst = Math.Round(dd, 1, MidpointRounding.AwayFromZero);

首先: 31.1

其次: 31.2

有人能解释一下为什么吗?


3
因为(float)31.15不等于(double)31.15,浮点数运算几乎总会产生舍入误差。特别是双精度浮点数的舍入与单精度浮点数不同。 - MakePeaceGreatAgain
1
浮点数值的舍入误差是不可避免的。它们就像死亡和税收一样不可避免。: https://www.youtube.com/watch?v=PZRI1IfStY0 - Christopher
4
好的,不总是这样,这就是为什么存在“float”的原因。 - SᴇM
4
@i486 再次强调,这并不意味着你永远不应该使用 float - SᴇM
4
C# 的金徽章在哪里?我认为这应该在最初的17秒内就被关闭为重复问题,而不是5个小时之后! - YSC
显示剩余8条评论
2个回答

30

好的,Math.Round 需要 double 而不是 float,这就是为什么。

Math.Round(ff, 1, MidpointRounding.AwayFromZero);
等于。
Math.Round((double)ff, 1, MidpointRounding.AwayFromZero);

如果我们检查(double)ff的值

Console.Write(((double)ff).ToString("R"));

我们将看到舍入误差的实际应用。

31.149999618530273

最终,Math.Round(31.149999618530273, 1, MidpointRounding.AwayFromZero) == 31.1(按预期)


我很好奇为什么(double)ff的值确切为31.14999961853027而不是31.150000000000000 - Gonzalo Lorieto
7
“double”是二进制的,“0.15 == 15 / 100 = 3 / 20”在二进制表示中是一个循环小数。 - Dmitry Bychenko
1
@GonzaloLorieto https://dev59.com/uXRB5IYBdhLWcg3wc280 - Camilo Terevinto
1
@GonzaloLorieto 你可以在这里阅读 -> 浮点运算 - SᴇM

15

在浮点数中,所有数字在内部都表示为分母为2的幂次方的分数。

(这类似于十进制实际上是具有10的幂作为分母的分数。因此,31.15只是写作分数3115/100的一种方式)

在浮点数中,31.15必须在内部表示为二进制数。最接近的二进制分数是:1111.1001001100110011001100110011001100110011001100110011001100...重复

1100会不断循环(重复),因此根据是否存储在双精度或单精度中,该数字将被截断为不同的长度。在单精度中被截断为24位数字,在双精度中则为53位。

Exact:  1111.100100110011001100110011001100110011001100110011001100110011001100...forever
Float:  1111.10010011001100110011
Double: 1111.1001001100110011001100110011001100110011001100110
因此,您可以看到这个数字转换成的双精度浮点数实际上比它转换成的单精度浮点数稍微大一些。因此很明显它不一定会四舍五入为相同的数字,因为它本身就不是相同的数字。

因此,您可以看到这个数字转换成的双精度浮点数实际上比它转换成的单精度浮点数稍微大一些。因此很明显它不一定会四舍五入为相同的数字,因为它本身就不是相同的数字。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接