如 "C++中带负数的整数除法舍入 "所述,在 C99 之前的 C 中(即在 C89 中)和在 C++11 之前的 C++ 中(即在 C++98 和 C++03 中),对于一个整数除法计算,其中任意一个操作数为负数,余数的符号(或等效地,商的舍入方向)是由实现定义的。
然后是标准函数 std::div
,它被指定为 向零截断商(即余数与被除数(分子)具有相同的符号)(例如,请参见"div()库函数的目的是什么?"}这个答案)。
这是glibc的div()
函数代码(源代码)(也引用于“div函数在stdlib.h中有用吗?”):
(注意:div_t
被定义为:
typedef struct
{
int quot;
int rem;
} div_t;
-- 结束注释。)
/* Return the `div_t' representation of NUMER over DENOM. */
div_t
div (numer, denom)
int numer, denom;
{
div_t result;
result.quot = numer / denom;
result.rem = numer % denom;
/* The ANSI standard says that |QUOT| <= |NUMER / DENOM|, where
NUMER / DENOM is to be computed in infinite precision. In
other words, we should always truncate the quotient towards
zero, never -infinity. Machine division and remainer may
work either way when one or both of NUMER or DENOM is
negative. If only one is negative and QUOT has been
truncated towards -infinity, REM will have the same sign as
DENOM and the opposite sign of NUMER; if both are negative
and QUOT has been truncated towards -infinity, REM will be
positive (will have the opposite sign of NUMER). These are
considered `wrong'. If both are NUM and DENOM are positive,
RESULT will always be positive. This all boils down to: if
NUMER >= 0, but REM < 0, we got the wrong answer. In that
case, to get the right answer, add 1 to QUOT and subtract
DENOM from REM. */
if (numer >= 0 && result.rem < 0)
{
++result.quot;
result.rem -= denom;
}
return result;
}
正如您所看到的,大段注释后面有一个测试,其目的是在内置除法向负无穷截断而不是向零截断时“修正”结果。
现在问题来了:
那段代码中没有错误吗?
让我们先考虑示例调用div(42, -5)
。在数学上,42/-5恰好是-8.4,因此在C89和C++03中,42 / -5
理论上可以产生-8
(截断)或-9
(向下取整),具体取决于实现方式。阅读代码:
- 如果
42 / -5
的结果是-8
,那么42 % -5
的结果是2
(因为42 == -8 * -5 + 2
),所以测试条件是(42 >= 0 && 2 < 0)
,这是不成立的,因此上述函数返回-8
和2
,符合要求; - 如果
42 / -5
的结果是-9
,那么42 % -5
的结果是-3
(因为42 == -9 * -5 + -3
),所以测试条件是(42 >= 0 && -3 < 0)
,这是成立的,因此上述函数返回 "修正后的"-9 + 1
和-3 - -5
,即-8
和2
,符合要求。
现在让我们考虑调用 div(-42, 5)
(符号反转):
- 如果
-42 / 5
的结果为-8
,则-42 % 5
的结果为-2
(因为-42 == -8 * 5 + -2
),因此测试为(-42 >= 0 && -2 < 0)
,这是不正确的,上述函数返回所需的-8
和-2
; - 如果
-42 / 5
的结果为-9
,则-42 % 5
的结果为3
(因为-42 == -9 * 5 + 3
),因此测试为(-42 >= 0 && 3 < 0)
,这是不正确的!上述函数返回-9
和3
而不是-8
和-2
!
我简直无法相信自1992年或1990年以来,这样的一个错误会一直被忽视(显然有人试图“修复”它,但似乎仍然不正确,因为
div(-42, 5)
可以返回-10
和8
)...可以说,大多数实现默认情况下都是向零截断的(并且所有实现都从C99和C++11开始要求这样做,所以在最新的标准中问题是“无关紧要的”1),因此这个错误不会在它们上面表现出来,但是...也许我错过了什么?谢谢你提供任何见解。
1 (编辑) 关于"C++11和C99(及更高版本)中的问题不再存在": 因此,在这些标准中,内置除法操作需要向零截断,因此我们永远不需要调整结果,但是这是否意味着当前的实现比所需更加复杂和低效?"大注释"已经过时,if
测试也无用,所以该部分是否应完全删除?
div
的目的正是为了封装依赖于实现的操作,以给出独立于实现的结果。@ouah 你的链接与我问题中的最后一个链接相同(http://minilib-c.googlecode.com/svn-history/r2/trunk/stdlib/div.c),并且如我所说,对于num == -42
和denom == 5
,如果你得到r.quot = -9
和r.rem = 3
,新的“修正”--r.quot
和r.rem += 5
将会给出r.quot == -10
和r.rem == 8
(而不是r.quot == -8
和r.rem == -2
)... - gx_