【问题标题】:Modulo operator with negative values [duplicate]具有负值的模运算符 [重复]
【发布时间】:2011-11-27 12:19:59
【问题描述】:

为什么要这样操作:

std::cout << (-7 % 3) << std::endl;
std::cout << (7 % -3) << std::endl;

给出不同的结果?

-1
1

【问题讨论】:

    标签: c++


    【解决方案1】:

    在这种情况下(即当一个或两个操作数为负时)的 符号 是实现定义的。规范在 §5.6/4 (C++03) 中说,

    二元 / 运算符产生商,二元 % 运算符产生第一个表达式除以第二个表达式的余数。如果 / 或 % 的第二个操作数为零,则行为未定义;否则 (a/b)*b + a%b 等于 a。如果两个操作数都是非负数,则余数是非负数; 如果不是,余数的符号是​​实现定义的

    就 C++03 而言,这就是语言要说的全部内容。

    【讨论】:

    • 这就是不得不要说的所有语言。 C++11(如 C99)采用 Fortran 舍入为零的规则(即删除小数部分)。在实践中,所有硬件很久以前就采用了 Fortran 规则,所以所有的实现无论如何都已经这样做了。 (“实现定义”的目的是允许 C/C++ 做硬件所做的任何事情。我认为一些非常旧的硬件总是向下舍入,所以-7/3 会导致-3,但那将是在非常遥远的过去。)
    • @JamesKanze:如果 b 是 2 的幂,过去可以将 n % b 计算为 n &amp; (b-1)。新标准要求将其计算为n &lt; 0 ? n | -b : n &amp; (b-1)。同样,n / b 不能写成简单的移位,即使在支持算术移位的硬件上也是如此;相反,在这样的系统上,像n/16 这样的表达式(如果nint32)必须写成n &lt; 0 ? (n+15) &gt;&gt; 4 : n &gt;&gt; 4。可怕的标准,恕我直言。请注意,由于非二次方除法本来就很慢,因此强制欧几里得行为不会减慢多少。
    • @JamesKanze:不是在优化除以二的幂运算作为班次的情况下现有硬件的行为。在旧规则下,foo /= 16 可以写成asr [dword foo],4。在新规则下,最佳表示需要更多的指令 [也许mov eax,[foo] / mov ebx,eax, asr eax,31 / lsr eax,28 / add eax,ebx / asr eax,4 / mov [foo],eax] 不像除法指令那么慢,但比简单的移位要多得多。
    • @supercat 抱怨人类可读语言中的算术函数的工作方式,因为必须在机器代码中执行多少指令才能使其保持一致,而不是使用仅简化操作的 hacky 快捷方式所有可能值的一小部分?哇。哇。 慢拍
    • @CptRobby 您似乎忘记了 C 的整个设计理念都是围绕着 hacky 快捷方式来生成更快的汇编代码。
    【解决方案2】:

    来自 ISO14882:2011(e) 5.6-4:

    二元 / 运算符产生商,二元 % 运算符 产生第一个表达式除以的余数 第二。如果 / 或 % 的第二个操作数为零,则行为是 不明确的。对于整数操作数, / 运算符产生代数商,并丢弃任何小数部分;如果商 a/b 是 可以用结果的类型来表示,(a/b)*b + a%b 等于 a。

    剩下的就是基础数学:

    (-7/3) => -2
    -2 * 3 => -6
    so a%b => -1
    
    (7/-3) => -2
    -2 * -3 => 6
    so a%b => 1
    

    注意

    如果两个操作数都是非负数,则余数是非负数;如果 不是,余数的符号是​​实现定义的。

    来自 ISO14882:2003(e) 的 ISO14882:2011(e) 中不再存在

    【讨论】:

    • ISO 14882:2003 中没有表达式“代数商”;那里的表达式只有“商”(实现定义是-7/3 的结果是-2 还是-3)。
    • 我认为无论你用哪种方式和多少次剥那只猫的皮,基本事实是带符号操作数的除法和取模是实现定义的。总有某种形式的“选择哪条路”。该报价单末尾的保证身份是重要的。 (虽然我认为 C99 实际上可能会解决这个问题。)
    • @JamesKanze:C++03 仍然包含实现定义,是 C++11 删除了它。 (并且基本上要求部门遵循 fortran)
    • 很好的答案,在否定论点的情况下,我没有意识到% 的这个问题。
    • @KerrekSB 现在在 C++11 中定义。
    【解决方案3】:
    a % b
    

    在 C++ 中默认:

    (-7/3) => -2
    -2 * 3 => -6
    so a%b => -1
    
    (7/-3) => -2
    -2 * -3 => 6
    so a%b => 1
    

    在python中:

    -7 % 3 => 2
    7 % -3 => -2
    

    在 c++ 中到 python:

    (b + (a%b)) % b
    

    【讨论】:

    • 对于负数的结果符号没有“C++ 默认值”。
    猜你喜欢
    • 2014-06-01
    • 2020-11-30
    • 2012-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-09
    相关资源
    最近更新 更多