C99の前のC(すなわちC89)およびC++ 11より前のC++(つまりC++ 98およびC++ 03)で引用されているように、いずれかのオペランドが負の整数除算の場合剰余の符号(または商の丸めの方向)は、実装定義です。glibc `div()`コードのバグ?
ゼロ向かって商を切り捨てるに指定されている標準関数std::div
(すなわち残りは被除数(分子)と同じ符号を有する)(例えばthis answer to "what is purpose of div() library function?"参照)来ます。ここで
はdiv()
のためのglibcのコードは、(source)(また、 "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;
}
大きなコメントブロックの後にテストがあります。その目的は、組み込みのdivisi上の方がゼロに向かってではなく、無限に向かって切り捨てられます。
今の質問:そのコードにバグが
ありませんか?
最初に、div(42, -5)
という呼び出しを考えてみましょう。 "数学では" 42/-5は正確-8.4ので、理論的にはC89およびC++ 03 42/-5
は、実装に応じて(床の)-8
(切り捨て)またはのいずれかをもたらす可能性があります。コードを読み取る:
42/-5
場合収率-8
次いで42 % -5
収率2
(42 == -8 * -5 + 2
など)ので、テストが真でないと望んでいたように上記関数は、-8
と2
を返す(42 >= 0 && 2 < 0)
あります。試験は、が真ある(42 >= 0 && -3 < 0)
ので、上記関数が返すように望んでいたよう- 次いで
42 % -5
収率-3
(42 == -9 * -5 + -3
など)収率42/-5
場合、-9 + 1
と-3 - -5
、すなわち-8
と2
を「修正」。
しかし、今のは、コールdiv(-42, 5)
(逆サイン)を検討してみましょう:
-42/5
もし利回り(-42 == -8 * 5 + -2
など)-8
その後、-42 % 5
利回り-2
を、そのテストは本当ではない(-42 >= 0 && -2 < 0)
と上記の関数であります必要に応じて-8
と-2
を返します。-42/5
もし利回りその後、-42 % 5
利回り3
(-42 == -9 * 5 + 3
など)、テストはない真である...(-42 >= 0 && 3 < 0)
ですので!上記の関数は-8
と-2
の代わりにと3
を返します。それは「REMはNUMERの反対の符号を持っている」、しかしそれは巨大な簡素化を行う際「これはすべての沸騰補正が必要な状況であることを言う権利たときに最初に上記のコード内のコメントは思わ
NUMER> = 0でREM <が0の場合は、"0、ただしREM> 0の場合は"を省略するので、間違っている(不完全であると思われる) "上記の例では3
)。
おそらく、ほとんどの実装はデフォルトでゼロに向かって切り捨ててきた...私はほとんど(どうやらsomeone tried to "fix" itしかしdiv(-42, 5)
が-10
と8
を返す可能性があるため、それはまだ間違っているようだ)、このようなバグは1992年か1990年以来気付か残っているだろうと信じてすることはできません(そして、C99とC++ 11から始める必要がありますので、問題は最新の標準で "moot"です)バグはそれらには現れませんが、ここに何かが見つからない?
ありがとうございました。
として (編集)「は、問題はC++ 11とC99(以降)に議論の余地がある」:従って、これらの規格に内蔵分割向かって切り捨てることが要求されます結果を調整する必要はありませんが、現在の実装が必要以上に複雑なであることを意味しますとは不必要に非効率的です? 「大きなコメント」は廃止され、if
テストは役に立たないので、その部分だけを完全に削除しないでください。
モジュロ演算子は実装依存です。私はそれがこの問題の主要な要点だと思う。 – Jiminion
@Jim 'div'の目的は、インプリメンテーション依存の操作をカプセル化して、実装に_independent_結果を与えることです。 @ouahあなたのリンクは私の質問(http://minilib-c.googlecode.com/svn-history/r2/trunk/stdlib/div.c)の最後のリンクと同じコードであり、私が言ったように、 'numあなたが 'r.quot = -9'と' r.rem = 3'を受け取った場合、 '== -42'と' denom == 5'となり、新しい ''修正 '' --r.quot'と '' r.rem + 'r.rem == -10'と' r.rem == 8 '( 'r.quot == -8'と' r.rem == -2'の代わりに)を返します... –
私は彼らがそれを別の方法で見たと思う。彼らはdivを正確に定義しました。彼らが認めた%は、実装に依存していました。 (とにかく混合記号演算子でモジュロ演算を行うのはなぜですか?これは重大な問題ではないと思いますので、一貫性についての懸念はほとんどありませんでした。 – Jiminion