glibc 'div()' 代码中的错误
Bug in glibc `div()` code?
如"整数除法四舍五入与负数在C++"中引用,在 C 中,在 C99 之前(即在 C89 中(和在 C++ 中在 C++11 之前(即在 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
!
上面代码中的注释首先似乎是正确的,因为它说需要更正的情况是"REM 具有与 NUMBER 相反的符号",但随后它进行了巨大的简化"这一切都归结为:如果 NUMER>= 0,但 REM <0,我们得到了错误的答案",这对我来说似乎是错误的(不完整(,因为它省略了"如果 NUMER <0, 但 REM> 0"(在上一个示例中-42
和 3
(。
我几乎不敢相信这样的错误自 1992 年或 1990 年以来一直没有被注意到(显然有人试图"修复"它,但它似乎仍然不正确,因为div(-42, 5)
可以返回-10
和8
(......可以说,大多数实现默认都已截断为零(并且所有实现都需要从 C99 和 C++11 开始这样做,因此该问题在最新的标准 1 中"没有意义"(,因此错误不会出现在它们身上,但仍然......也许我在这里错过了什么?
感谢您的任何见解。
1(编辑(至于"这个问题在C++11和C99(及更新版本(中没有实际意义":因此,在这些标准中,内置除法需要截断为零,所以我们永远不需要调整结果,但这是否意味着目前的实现比需要的更复杂,并且不必要的低效?"大评论"已经过时,if
测试也没用,那么这部分不应该完全删除吗?
作为代码的原始作者,我不得不说:你是对的。 它坏了。 我们没有系统在测试中表现"错误",我可能在当天写了太晚(或早......(或其他什么。
我们被更新的标准所拯救,整个事情应该被清理掉,如有必要,也许可以用一小#ifdef
(和更正的调整代码(来代替 C99 之前。
(我还要注意的是,原版没有用 GNU 风格的缩进缩进:-(
有理由,为什么它不能被实现定义以及它必须是什么。
TLDR:余数符号必须与除法符号匹配。
理由:
当恢复除以值时,知道除法结果、余数和除数,它的余数必须与除数除积相加。如果 remander 标志是特定于实现的,那么总和的操作也应该是特定于实现的,不是吗?或者至少在除法后有一个代码,如果它不正确,它将修复余数符号。
- 错误处理.将系统错误代码映射到泛型
- 为什么我在使用void函数时得到错误代码C2276
- 尝试链接我的着色器时,我收到错误代码"error c5145 must write to gl_position"
- 逻辑运算符上出现错误代码 a')'
- 'val' Arduino 错误代码之前的预期'('
- 我在贪吃蛇游戏中收到了错误代码 -1073741571
- 根据 GetLastError 直接写入磁盘会导致错误代码 5
- 当我选择大于 720 的矩阵大小时,程序退出并显示错误代码.可能是什么原因?
- 创建进程 API 失败,在窗口 122 上出现错误代码 10
- 使用另一个函数调用一个函数(都在类中)时出现问题.没有错误代码C++
- 当命令失败时,gzip 会在 C++ 中返回错误代码吗?
- 从 C++ 调用 cURL 命令会返回意外的错误代码,如 1792 和 6656
- WSALookupServiceBegin() 上的错误代码 10022(无效的 arugment)
- C++ Schannel POST 400 错误代码错误请求
- openCL-创建子缓冲区返回错误代码13
- 更新了Runge-Kutta(RK4)C++错误代码中的二阶DE
- 获取有关使用未初始化内存的错误代码
- 排序程序的意外错误代码
- 正在将DJI错误代码记录到流中
- 来自API的错误代码..处理什么是好的做法