繁体   English   中英

glibc`div()`代码中的错误?

[英]Bug in glibc `div()` code?

如“ 在C ++中使用负数进行整数除法 ”中所述,在C99之前的C中(即在C89中)和在C ++之前的C ++中(即在C ++ 98和C ++ 03中),用于整数除法计算,其中两个操作数都是负数,余数的符号(或等效地,商的舍入方向)是实现定义的

然后是标准函数std::div ,它被指定为将商截断为零 (即余数与被除数(分子)具有相同的符号)(例如,参见“div()函数的目的是什么” ?“ )。

这是glibc的div()代码( 源代码 )(也引用了“ 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 (截断的)或-9 (地板),具体取决于实现。 阅读代码:

  • 如果42 / -5产生-8然后42 % -5产生2 (因为42 == -8 * -5 + 2 ),那么测试是(42 >= 0 && 2 < 0)这是不正确的以上函数返回-82 ,如所需;
  • 如果42 / -5收益-9那么42 % -5收益-3 (如42 == -9 * -5 + -3 ),所以测试是(42 >= 0 && -3 < 0)真的,所以上面的函数返回“校正的” -9 + 1-3 - -5 ,即-82 ,如所需。

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

  • 如果-42 / 5产生-8然后-42 % 5产生-2 (如-42 == -8 * 5 + -2 ),那么测试是(-42 >= 0 && -2 < 0)这不是true和上面的函数返回-8-2 ,如所需;
  • 如果-42 / 5得到-9那么-42 % 5 3 (as -42 == -9 * 5 + 3 ),所以测试是(-42 >= 0 && 3 < 0)其中...... 不是真正! 以上函数返回-93而不是-8-2

上面代码中的注释首先似乎是正确的,当它说需要修正的情况是“REM与NUMER符号相反”时,但它会大大简化 “这一切归结为:如果NUMER> = 0 ,但REM <0,我们得到了错误的答案“,这对我来说似乎是错误的(不完整),因为它省略了 ”如果NUMER <0,但REM> 0“(前一个例子中为-423 )的情况。

我很难相信,自1992年或1990年以来,这样的错误一直未被注意到(显然有人试图“修复”但它似乎仍然不正确,因为div(-42, 5)可以返回-108 )......可以说,默认情况下,大多数实现都被截断为零(并且从C99和C ++ 11开始都需要这样做,因此在最新的标准1中问题是“没有实际意义”)因此bug不会在它们上面显示,但是还是......也许我在这里错过了一些东西?

感谢您的任何见解。


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

作为代码的原作者,我不得不说:你是对的。 它坏了。 我们没有任何系统表现出“错误的方式”进行测试,而且我可能在当天或类似的事情上写了太多(或早期......)。

我们被更新的标准所保存,整个事情应该被清除掉,如果有必要,可以使用小的#ifdef (并修正调整代码)用于C99之前。

(另外我会注意到原始版本没有缩进GNU风格的缩进:-))

但在这种情况下NUMER等于-42,(所以它是负的)这一自动进行的条件为假。 实际上,如果您尝试执行包含该函数的简单代码,您将看到(调试它)if语句:

// false in this case, numer = -42 and result.rem = -2
if(numer >= 0 && result.rem < 0)

在这里您可以看到编译结果: 在此处输入链接描述

我希望答案是正确的,并帮助您理解错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM