为什么C#中的模运算符对于double类型不起作用?

12

考虑以下代码:

double x,y;
x =120.0;
y = 0.05;

double z= x % y;

我尝试了这个代码,并期望得到的结果是0,但实际输出为0.04933333。

然而,

x =120.0;
y = 0.5;
double z= x % y;

确实得到了正确的结果0。

这里发生了什么?

我尝试了Math.IEEERemainder(double, double),但它也没有返回0。这里发生了什么?

另外,顺便问一下,在C#中找到余数的最合适的方法是什么?


知道您要达到什么目的会很有趣。使用浮点数取模永远不是一个好主意,正如答案中已经说明的那样。 - VVS
相关:Python浮点数取模(虽然是不同的语言,但问题——浮点数误差——是相同的) - user202729
6个回答

16

由于其存储格式,double不能完全像输入或显示的那样准确地存储每个值。数字的人类表示通常为十进制格式,而double基于二进制系统。

double中,120被精确存储,因为它是整数值。但是0.05不是。 double被近似为可以表示最接近的0.05的数字。0.52的幂次方(1/2),所以它可以被准确地存储,从而避免了舍入误差。

如果要使所有数字与您在十进制系统中输入/显示的方式完全相同,请使用decimal

decimal x, y;
x = 120.0M;
y = 0.05M;

decimal z = x % y;  // z is 0

1
一个 double 总是一个精确的值。决定它的值的操作可能是不精确的。double a = 0.1; --> a 有一个精确的值,可能略微不同于数学上的 0.1。double b = 0.125; --> b 有一个精确的值,与数学上的 0.125 相同。仍然建议使用 decimal - chux - Reinstate Monica
5
“exact value that may slightly differ” 可以翻译为“略有差异的非精确值”,或简单地翻译成“不精确的”。 - Stefan Steinegger
2
@chux:每个数字值都有其分辨率。在这个上下文中,“精确”意味着该值正好存储为您输入的值或显示的值。整数是精确值。如果您键入7(例如在代码中的常量中),它不会在幕后存储6.999999999999999。小数也是如此。双精度浮点数则不同。由于其存储格式,它无法精确地存储输入/显示的值。 - Stefan Steinegger
1
“doubles are never exact values”是绝对的胡说八道。考虑到C#使用IEEE754,这个答案可以得到相当的升级。 - Bathsheba
2
@Bathsheba:实现IEEE754标准并不能确保双精度浮点数能够精确存储十进制值。请看我的最后一条评论,了解在使用十进制系统输入/显示值与双精度浮点数的存储格式之间存在的差异。如果你不相信我所说的,请使用问题中的代码自行验证。 - Stefan Steinegger
显示剩余4条评论

11

你可以这样做:

double a, b, r;

a = 120;
b = .05;

r = a - Math.floor(a / b) * b;

这应该会有所帮助 ;)


1

1

我相信如果你使用 decimal 也会正常工作。


0

取模应该只用于整数。余数来自欧几里得除法。使用双精度浮点数时,可能会导致意想不到的结果。

请参阅本文


2
你可以安全地使用取模运算符处理小数。 - Stefan Steinegger

0

这就是我们使用的... :)

 public double ModuloOf(double v1, double v2)
    {
        var mult = 0;

        //find number of decimals
        while (v2 % 1 > 0)
        {
            mult++;
            v2 = v2 * 10;
        }

        v1 = v1 * Math.Pow(10, mult);

        var rem = v1 % v2;
        return rem / Math.Pow(10, mult);
    }

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