glibc`div()`代码中的Bug? 如“C++99”(即C89)和C++前的C++(即C++ 98和C++ 03)中的“P”>,对于整数运算,其中任一个操作数都是负的,其余的符号(或等价地,商的舍入方向)是定义的实现。
然后是标准函数,该函数指定将商截断为零(即余数与被除数(分子)具有相同的符号)(参见示例) 以下是glibc为glibc`div()`代码中的Bug? 如“C++99”(即C89)和C++前的C++(即C++ 98和C++ 03)中的“P”>,对于整数运算,其中任一个操作数都是负的,其余的符号(或等价地,商的舍入方向)是定义的实现。,c++,c,implementation,glibc,integer-division,C++,C,Implementation,Glibc,Integer Division,然后是标准函数,该函数指定将商截断为零(即余数与被除数(分子)具有相同的符号)(参见示例) 以下是glibc为div()()(也在“”中引用)编写的代码: (注:div\u t定义为: typedef struct { int quot; int rem; } div_t; --尾注。) /*返回NUMER对DENOM的'div\t'表示形式*/ 分区 部门(数字、名称) 整数; { 分割结果; result.quot=数值/分母; result.rem=数值%deno
div()
()(也在“”中引用)编写的代码:
(注:div\u t
定义为:
typedef struct
{
int quot;
int rem;
} div_t;
--尾注。)
/*返回NUMER对DENOM的'div\t'表示形式*/
分区
部门(数字、名称)
整数;
{
分割结果;
result.quot=数值/分母;
result.rem=数值%denom;
/*ANSI标准规定“QUOT”=0&&2<0)
,这是不正确的,上述函数根据需要返回-8
和-2
-42/5
产生-9
,那么-42%5
产生3
(正如-42=-9*5+3
),那么测试是(-42>=0&&3<0)
,这是不正确的!上述函数-9
和3
,而不是-8
和-2
-42
和3
)
我很难相信这样一个bug自1992年或1990年以来一直没有被注意到(显然,但它似乎仍然不正确,因为div(-42,5)
可能返回-10
和8
)……可以说,大多数实现在默认情况下都被截断为零(从C99和C++11开始,所有人都必须这样做,所以这个问题在最新的标准1中是“没有意义的”)所以这个错误不会在他们身上出现,但仍然……也许我在这里遗漏了什么
谢谢你的见解
1(编辑)至于“这个问题在C++11和C99(及更新版本)中是没有意义的”:因此,在这些标准中,内置除法需要向零截断,因此我们永远不需要调整结果,但这是否意味着当前的实现比需要的更复杂,并且不必要地效率低下是过时的,
如果
测试没有用,那么该部分不应该完全删除吗?作为代码的原始作者,我不得不说:你是对的。它已经损坏了。我们没有任何系统在测试时表现出“错误的方式”,我可能在当天写上述内容太晚了(或太早了…)
我们被更新的标准保存了下来,整个东西都应该清理干净,如果需要的话,可以为C99之前的版本添加一个小的#ifdef
(和修正的调整代码)
(我还将注意到,原始版本没有使用GNU样式的缩进进行缩进:-))带符号操作数的模运算符依赖于实现。我认为这是这个问题的主要症结所在。@Jim
div
的目的正是封装依赖于实现的操作,以给出独立于实现的结果。@ouah您的链接与我问题中的最后一个链接()正如我所说,对于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)…我想他们是从另一个角度看问题的。他们精确地定义了div的w.r.t.%,他们承认div是依赖于实现的。(为什么要使用混合符号运算符进行模运算呢?我不认为这是一个严重的问题,所以不太关心一致性。@是的,至少它尝试处理它:)增加的修正看起来更针对-42/-5
,它“可以”给出9
和3
,而不是8
和-2
(欧几里德除法)(但这几乎和想象42/5
给出9
和-3
而不是8
和2
一样牵强(无论如何,这是标准所禁止的。)Chris Torek!就是你!非常感谢你的回答!真的我不希望有更好的来源:)(至于缩进,可能是原始的更接近这个?)(同时,我一直在做更多的网络搜索,并发现(XOR似乎合法,但调整错误),尤其是(!)它似乎能正确处理42/-5和-42/5!)是的,这基本上是原始的(K&R-ish,“高凸度”缩进:-)…最终成为FreeBSD的风格(9)。我以前从未见过Sprite版本,也没有见过Vrije Universiteit版本。xor似乎有风险(签名)int
。static
方法是有效的,但在所有情况下,运行时测试的需求似乎都是杂乱无章的。即使我们不能指望C99,也最好有一个特定于编译器的定义来描述整数除法行为,因为很多机器一开始就“按照C99想要的方式运行”。事实上,我已经提交了一份文件。(顺便说一句,Vrije Universiteit one很接近,但例如,在一台总是将商朝着-无穷大铺底的机器上,div(-10,5)
,它产生{-2,0}
,将被错误地“更正”为{-1,-5}
。)
/* 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;
}