取模运算符的变化

4

C++03中的5.6/4规定:“如果两个操作数都为非负数,则余数为非负数;否则,余数的符号由实现定义74)。”。

其中Note 74是这样描述的

根据正在进行的ISO C修订工作,整数除法的首选算法遵循在ISO Fortran标准ISO/IEC 1539:1991中定义的规则,其中商总是朝着零舍入。

C++0x规定 -

C++0x中的5.6/4-“对于整型操作数,/运算符产生代数商,并舍弃任何小数部分;79如果商a/b可以在结果类型中表示,则(a/b)*b + a%b等于a。

Note 79说

这通常称为向零截断。

所以我有两个问题:

  • 有人能解释一下“向零截断”的概念吗?

  • C++0x中使用负操作数进行取模运算是否是已定义的行为?


2
唉,新定义仍然没有最常需求的基本模数特性:A mod B 应该保证产生一个在 0 和 B 之间(包括 0 但不包括 B)的值。 - swestrup
2
@swestrup:这一切都关乎优先级。如果您要求A%B∈[0,B),并且您还要求(A/B)*B+A%B==A,则A/B必须具有一种反常的舍入规则,即向-∞截断。在这种舍入下,10/-3 == -4,并且(-A)/B != -(A/B)。 - Philip Potter
1
是的,但这种“反常”的舍入方案在使用有符号整数时,在绝大多数情况下都是必需的。我还没有看到任何使用案例表明当前定义非常有用。实际上,通常是通过硬件高效来证明其合理性,这是因为硬件也会出错。Perl的模运算符是正确的,除非使用整数模式... - swestrup
@PhilipPotter:我认为(A+m)%m==A%m(A+d)/d==(A+d)+1这两个关系比(-A)/B==-(A/B)更有用。在使用整数运算绘制图形时,将其转换以使0位于窗口中间时,正负数之间的过渡可能会出现非常丑陋的接缝;如果/运算符向负无穷舍入,则可以避免这样的接缝。请注意,通过向负无穷舍入的除法语义,在对带符号数除以2的幂或取模时速度会更快。 - supercat
@swestrup:如果我能自己选择,而且一种语言不会强制要求“-1%m ==(m-1)”,我会放宽规则,使得当且仅当“n”和“m”都是正数或“n”是“m”的倍数时,“n%m”可以任意返回正负模数,并且“n / m”可以任意向上或向下舍入,无需要求结果即使对于给定值组合也保持一致。这样的规则将允许应用程序执行涉及常量二次幂除数或基数的计算,而无需包括有符号数学清理代码。 - supercat
显示剩余2条评论
3个回答

6

向零取整是指通过选择最接近零的下一个整数将实数转换为整数。等效地,您将数字写下来,并忽略小数点后的所有内容,无论数字是正数还是负数。

考虑11/4 = 2.75--如果你向零截断这个数,你得到2。

考虑-11/4或11/-4 = -2.75--如果你向零截断这个数,你得到-2。

对于某些数学运算,(a/b)*b + a%b == a非常重要。如果我们必须使这个方程成立,并且我们也接受整数除法向零截断,那么我们可以推断出%运算符的操作如下:

a == 11, b == 4:
a/b == 2, 2*4 + a%b == 11, therefore a%b == 3.

a == -11, b == 4:
a/b == -2, -2 * 4 + a%b == -11, therefore a%b == -3.

a == 11, b == -4:
a/b == -2, -2 * -4 + a%b == 11, therefore a%b == 3.

a == -11, b == -4:
a/b == 2, 2 * -4 + a%b == -11, therefore a%b == -3.

一开始可能有点困惑,但是C++0x正在使用公式(a/b)*b + a%b == a来定义a%b运算符的行为。即使对于负数,这个公式也成立,因此a%b在负数情况下也是被定义的。


3

a) 考虑 (±5)/(±3) -> ±1。在数轴上:

  (-5)/3                                     5/3
     5/(-3)                               (-5)/(-3)
      =                                       =
    -1.66 --> -1                       1 <-- 1.66
       v       v                       v       v
-  +  -  -  -  +  -  -  -  +  -  -  -  +  -  -  -  +  -
   |           |           |           |           |
  -2          -1           0           1           2

因此,四舍五入是朝着零的方向进行的。

b) 是的。由于现在对所有ab(除了b == 0)定义了a/b,并且(a/b)*b + a%b == a,所以a%b只有1个唯一解,因此%运算符也对所有ab(除了b == 0)都是良好定义的。


为什么叫做“四舍五入”?这不是截断吗? - Chubsdad
@chubsdad:术语“舍入”比你想象的要广泛得多。请参阅http://en.wikipedia.org/wiki/Rounding#Rounding_to_integer - kennytm

1

a) "向零截断"简单地意味着任何小数部分都被砍掉。截断后的数字始终至少与原数字一样接近0,通常更接近。这在负数中最为明显,但目标是使使用/%与负数变得不那么棘手(因为当前,任何实现都可以按照自己的方式处理)。-7/4可以有两种思考方式:余数为1的-2,或者余数为-3的-1。有编译器和处理器会以两种方式处理它。由于-7/4实际上是-1.75,“向零截断”将给出-1,因此后一种方式将成为标准。

b) 看起来就是这样。它一直是半定义的(“实现定义”),但这似乎是试图定义从一开始就应该成为标准的东西。


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