2013-07-22 12 views
7

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収率242 == -8 * -5 + 2など)ので、テストが真でないと望んでいたように上記関数は、-82を返す(42 >= 0 && 2 < 0)あります。試験は、が真ある(42 >= 0 && -3 < 0)ので、上記関数が返すように望んでいたよう
  • 次いで42 % -5収率-342 == -9 * -5 + -3など)収率42/-5場合、-9 + 1-3 - -5、すなわち-82を「修正」。

しかし、今のは、コール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)-108を返す可能性があるため、それはまだ間違っているようだ)、このようなバグは1992年か1990年以来気付か残っているだろうと信じてすることはできません(そして、C99とC++ 11から始める必要がありますので、問題は最新の標準で "moot"です)バグはそれらには現れませんが、ここに何かが見つからない?

ありがとうございました。


として (編集)「は、問題はC++ 11とC99(以降)に議論の余地がある」:従って、これらの規格に内蔵分割向かって切り捨てることが要求されます結果を調整する必要はありませんが、現在の実装が必要以上に複雑なであることを意味しますは不必要に非効率的です? 「大きなコメント」は廃止され、ifテストは役に立たないので、その部分だけを完全に削除しないでください。

+0

モジュロ演算子は実装依存です。私はそれがこの問題の主要な要点だと思う。 – Jiminion

+0

@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'の代わりに)を返します... –

+0

私は彼らがそれを別の方法で見たと思う。彼らはdivを正確に定義しました。彼らが認めた%は、実装に依存していました。 (とにかく混合記号演算子でモジュロ演算を行うのはなぜですか?これは重大な問題ではないと思いますので、一貫性についての懸念はほとんどありませんでした。 – Jiminion

答えて

5

コードの元の著者として、私は言う必要があります:あなたは正しいです。壊れている。私たちはテストのために "間違ったやりかた"をしたシステムを持っていませんでした。おそらく、その日に何か遅い(または早い...)と書いたでしょう。

私たちは新しい基準で保存されており、必要に応じてC99より前の小さな#ifdef(補正コード)を使用して全体を削除する必要があります。

(また、私は、元はGNUスタイルのインデント:-)でインデントされていないことに注意しましょう)署名のオペランドと

+0

Chris Torek!それはあなたです_!あなたの答えをありがとう!本当に私はより良いソースを望むことができませんでした:)(インデントについては、おそらくオリジナルはこれに近いです[div.c](http://svnweb.freebsd.org/base/head/lib/libc/stdlib/ div.c?view = co)?)(また、その間、私はより多くのウェブ検索を行っていて、[別の "修正"]を見つけました(http://www.beedub.com/Sprite093/src/lib/ c/stdlib/div.c)(XORは正当だと思われますが調整は間違っています)、特に** [a code from 1987](https://raw.github.com/7shi/minix-tools/master/lib/ (42/-5と42/5の両方を正しく扱っているようです) –

+1

はい、それは基本的にオリジナルです(K&R-ish、 "high Bosticity"インデント:-) ...最終的にはFreeBSDのスタイル(9)になりました)。私はSpriteバージョン、またはVrije Universiteitバージョンを見たことはありません。 xorはプレーンな(署名された) 'int'では危険なようです。 'static'メソッドは有効ですが、どのような場合でも、ランタイムテストの必要性はちょっと面倒です。 C99を数えることができなくても、整数除算の動作を記述するコンパイラ固有の#defineを持つことは良いことです。なぜなら、多くのマシンが最初にC99が望むように動作するからです。 – torek

+0

確かに。 **私は[glibcバグレポート](http://sourceware.org/bugzilla/show_bug.cgi?id=15799)を提出しています。**(ところで、Vrije Universiteitは近くにあったが、マシン{-2、0} 'を生成する' div(-10、5) 'は、' {-1、-5} 'に間違って"訂正 "されます...) –

-1

この場合、数値は-42と等しく(負であるため)、条件が自動的に偽になります。実際にあなたが見るであろう機能が含まれている簡単なコードを実行しようとした場合、そのif文(それをデバッグする):enter link description here

が、私はことを願っています:

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

はあなたがコンパイル結果を見ることができますが答えは正しいですし、エラーを理解するのに役立ちます。

+1

問題は明らかに、この場合に条件が偽であってはならないということです。 –

+0

申し訳ありませんが、問題を誤解しているようです。 @マークBはい。 –

関連する問題