【发布时间】:2013-07-21 15:37:35
【问题描述】:
正如“Integer division rounding with negatives in C++”中所引用的,在 C99 之前的 C(即 C89 中)和 C++11 之前的 C++(即 C++98 和 C++03)中,对于整数除法计算,其中任一操作数为负,余数的符号(或等价的商的舍入方向)是实现定义的。
然后是标准函数std::div,它指定将商向零截断(即余数与被除数(分子)具有相同的符号)(例如参见this answer to "what is purpose of div() library function?") .
这里是div() (source) 的 glibc 代码(也在“Is div function useful (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;
}
如您所见,在大注释块之后有一个测试,其目的是在内置除法截断为 -infinity 而不是为零时“纠正”结果。
现在的问题:
该代码中没有错误吗?
让我们首先考虑示例调用div(42, -5)。 “在数学中”42/-5 正好是 -8.4,因此理论上在 C89 和 C++03 中 42 / -5 可以产生 -8(截断)或 @ 987654336@(地板)取决于实施。阅读代码:
- 如果
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!
上面代码中的注释首先看起来是正确的,当它说需要更正的情况是“REM 有 NUMER 的相反符号”时,但随后它使 巨大的简化“这一切归结为:如果 NUMER >= 0,但 REM 它忽略了这种情况“如果 NUMER 0"(上例中为-42 和3)。
我几乎不敢相信这样的错误会在 1992 年或 1990 年之后一直被忽视(显然是 someone tried to "fix" it,但它似乎仍然不正确,因为 div(-42, 5) 可以返回 -10 和 8)...可以说,大多数实现默认情况下已经被截断为零(并且从 C99 和 C++11 开始,所有这些都需要这样做,所以这个问题在最新的标准 1 中是“没有实际意义的”)所以这个错误不会显示在他们身上,但仍然......也许我在这里遗漏了什么?
感谢您提供任何见解。
1(编辑) 至于“问题在 C++11 和 C99(及更高版本)中没有实际意义”:因此,在这些标准中,内置除法需要向零截断,因此我们永远不需要调整结果,但这是否意味着当前的实现比需要的更复杂并且不必要地低效 ? “大评论”已经过时,if 测试也没用,所以那部分不应该完全删除吗?
【问题讨论】:
-
带符号操作数的模运算符取决于实现。我认为这是这个问题的主要症结。
-
@Jim
div的目的正是为了封装依赖于实现的操作以给出实现-独立的结果。 @ouah 您的链接与我的问题 (minilib-c.googlecode.com/svn-history/r2/trunk/stdlib/div.c) 中的最后一个链接的代码相同,正如我所说,对于num == -42和denom == 5,如果您得到r.quot = -9和r.rem = 3,则新的“更正”@ 987654385@ 和r.rem += 5将给出r.quot == -10和r.rem == 8(而不是r.quot == -8和r.rem == -2)... -
我认为他们以另一种方式看待它。他们精确地定义了 div w.r.t。 %,他们承认这取决于实施。 (为什么还要对混合符号运算符进行模运算?我认为这不是一个重度踩踏的领域,因此几乎不需要担心一致性。
-
@ouah 是的,至少它会尝试处理它 :) 添加的更正看起来更针对
-42/-5,它“可能”给出9和3而不是8和@ 987654395@(欧几里得除法)(但这几乎就像想象42/5给出9和-3而不是8和2一样牵强附会(无论如何,这都是标准所禁止的)。
标签: c++ c implementation glibc integer-division