【问题标题】:Bug in glibc `div()` code?glibc `div()` 代码中的错误?
【发布时间】: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 &gt;= 0 &amp;&amp; 2 &lt; 0) 这是不正确的,上面的函数返回-82,随心所欲;
  • 如果42 / -5 产生-9 然后42 % -5 产生-3(如42 == -9 * -5 + -3),所以测试是(42 &gt;= 0 &amp;&amp; -3 &lt; 0) 为真,所以上面的函数返回根据需要“更正”-9 + 1-3 - -5,即 -82

但现在让我们考虑调用div(-42, 5)(符号倒置):

  • 如果-42 / 5 产生-8 然后-42 % 5 产生-2(如-42 == -8 * 5 + -2),所以测试是(-42 &gt;= 0 &amp;&amp; -2 &lt; 0) 这是不正确的,上述函数返回-8-2,随心所欲;
  • 如果-42 / 5 产生-9 然后-42 % 5 产生3(如-42 == -9 * 5 + 3),所以测试是(-42 &gt;= 0 &amp;&amp; 3 &lt; 0)...为真!而上面的函数返回-93而不是-8-2

上面代码中的注释首先看起来是正确的,当它说需要更正的情况是“REM 有 NUMER 的相反符号”时,但随后它使 巨大的简化“这一切归结为:如果 NUMER >= 0,但 REM 它忽略了这种情况“如果 NUMER 0"(上例中为-423)。

我几乎不敢相信这样的错误会在 1992 年或 1990 年之后一直被忽视(显然是 someone tried to "fix" it,但它似乎仍然不正确,因为 div(-42, 5) 可以返回 -108)...可以说,大多数实现默认情况下已经被截断为零(并且从 C99 和 C++11 开始,所有这些都需要这样做,所以这个问题在最新的标准 1 中是“没有实际意义的”)所以这个错误不会显示在他们身上,但仍然......也许我在这里遗漏了什么?

感谢您提供任何见解。


1(编辑) 至于“问题在 C++11 和 C99(及更高版本)中没有实际意义”:因此,在这些标准中,内置除法需要向零截断,因此我们永远不需要调整结果,但这是否意味着当前的实现比需要的更复杂并且不必要地低效 ? “大评论”已经过时,if 测试也没用,所以那部分不应该完全删除吗?

【问题讨论】:

  • 带符号操作数的模运算符取决于实现。我认为这是这个问题的主要症结。
  • @Jim div 的目的正是为了封装依赖于实现的操作以给出实现-独立的结果。 @ouah 您的链接与我的问题 (minilib-c.googlecode.com/svn-history/r2/trunk/stdlib/div.c) 中的最后一个链接的代码相同,正如我所说,对于 num == -42denom == 5,如果您得到 r.quot = -9r.rem = 3,则新的“更正”@ 987654385@ 和 r.rem += 5 将给出 r.quot == -10r.rem == 8(而不是 r.quot == -8r.rem == -2)...
  • 我认为他们以另一种方式看待它。他们精确地定义了 div w.r.t。 %,他们承认这取决于实施。 (为什么还要对混合符号运算符进行模运算?我认为这不是一个重度踩踏的领域,因此几乎不需要担心一致性。
  • @ouah 是的,至少它会尝试处理它 :) 添加的更正看起来更针对-42/-5,它“可能”给出93 而不是8 和@ 987654395@(欧几里得除法)(但这几乎就像想象42/5 给出9-3 而不是82 一样牵强附会(无论如何,这都是标准所禁止的)。

标签: c++ c implementation glibc integer-division


【解决方案1】:

作为代码的原作者,我不得不说:你是对的。它坏了。我们没有系统在测试中表现出“错误的方式”,而且我可能在当天太晚(或太早......)写了上面的内容。

我们被更新的标准拯救了,整个事情都应该被清理掉,如果需要的话,可能会用一个小的#ifdef(和更正的调整代码)来用于 C99 之前的版本。

(我还要注意,原件没有使用 GNU 风格的缩进 :-))

【讨论】:

  • 克里斯托雷克!那是!非常感谢您的回答!真的我不能指望更好的来源:)(至于缩进,也许原版更接近这个div.c?)(同时,我一直在做更多的网络搜索,发现another "fix"( XOR 似乎合法但调整错误),尤其是 a code from 1987 (!) 似乎正确处理 42/-5 -42/5!)
  • 是的,这基本上是原始的(K&R-ish,“高 Bosticity”缩进 :-) ...最终成为 FreeBSD 中的 style(9))。我以前从未见过 Sprite 版本,也没有见过 Vrije Universiteit 版本。使用普通(签名)int,xor 似乎有风险。 static 方法是有效的,但在所有情况下,运行时测试的需求似乎有点混乱。即使我们不能指望 C99,最好有一个编译器特定的#define 来描述整数除法行为,因为首先有这么多机器“按照 C99 的方式行事”。
  • 确实如此。 我已经提交了一个glibc bug report(顺便说一下,Vrije Universiteit 很接近,但是例如在一台总是将商向 -infinity 降低的机器上,div(-10, 5),产生{-2, 0} , 将被错误地“更正”为{-1, -5}...)
猜你喜欢
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
  • 2013-11-20
  • 2016-08-21
  • 2015-07-20
  • 2014-12-28
  • 2021-12-30
相关资源
最近更新 更多