web-dev-qa-db-ja.com

多くのスクリプト言語で整数除算が切り捨てられるのはなぜですか?

私がテストした言語では、- (x div y )-x div yと等しくありません。 Pythonで//、Rubyで/、Perl 6でdivをテストしました。 Cも同様の動作をします

divは通常 除算の結果の丸めdownとして定義されるため、その動作は通常仕様に従っています。 、ただし、divが符号に応じて異なる方法で動作し、 thisなどの混乱を引き起こすため、算術的な観点からはあまり意味がありませんPythonでどのように行われるかについて投稿してください

この設計決定の背後にある特定の理論的根拠はありますか、それともdivはそのように最初から定義されていますか?どうやら Guido van Rossumはコヒーレンシ引数を使用します Pythonでそれがどのように行われるかを説明するブログ投稿でですが、切り上げを選択した場合もコヒーレンシを持つことができます。

#Perl6のPMuriasによるこの質問IRCチャネル に触発された)

42
jjmerelo

浮動小数点演算は、数値アプリケーションを考慮してIEEE754によって定義され、デフォルトでは、非常に厳密に定義された方法で最も近い表現可能な値に丸められます。

コンピューターでの整数演算は、一般的な国際標準によってnot定義されています。言語(特にCファミリーのもの)によって許可される操作は、基礎となるコンピューターが提供するものすべてに従う傾向があります。一部の言語は特定の操作を他の言語よりも堅牢に定義しますが、当時の利用可能な(および一般的な)コンピューターでの実装が過度に困難または遅いことを避けるため、その動作に非常に厳密に従う定義を選択します。

このため、整数演算は、オーバーフロー(加算、乗算、および左シフトの場合)でラップアラウンドし、負の無限大に向かってラウンドする傾向があります不正確な結果を生成する場合(除算および右シフトの場合)。 これらは両方とも単純なtruncation整数のそれぞれの端で 2の補数のバイナリ算術;コーナーケースを処理する最も簡単な方法。

他の回答では、言語が除算とともに提供する剰余演算子またはモジュラス演算子との関係について説明します。残念ながら、彼らはそれを後方に持っています。 剰余は除算の定義に依存し、その逆ではありません、モジュラスは除算とは無関係に定義できます-両方の引数が正であり、除算が切り捨てられた場合、同じになる人々はめったに気づかない。

最新の言語のほとんどは、剰余演算子またはモジュラス演算子のいずれかを提供しますが、両方を使用することはほとんどありません。ライブラリ関数は、差を気にする人に他の操作を提供できます。つまり、剰余は被除数の符号を保持し、モジュラスは除数の符号を保持します。

37
Chromatix

理想的には、それぞれの_b>0_に対して、2つの操作divmodを満足させたいと思います。

  1. _(a div b) * b + (a mod b) = a_
  2. 0 <= (a mod b) < b
  3. _(-a) div b = -(a div b)_

ただし、これは数学的に不可能です。上記がすべて当てはまる場合、

_1 div 2 = 0
1 mod 2 = 1
_

これは(1)と(2)に対する一意の整数解であるためです。したがって、(3)によって、

_0 = -0 = -(1 div 2) = (-1) div 2
_

(1)により、

_-1 = ((-1) div 2) * 2 + ((-1) mod 2) = 0 * 2 + ((-1) mod 2) = (-1) mod 2
_

(2)と矛盾する_(-1) mod 2 < 0_を作成します。

したがって、(1)、(2)、および(3)の間でいくつかのプロパティを放棄する必要があります。

いくつかのプログラミング言語は放棄し(3)、divを切り捨てます(Python、Ruby)。

一部の(まれな)場合、言語は複数の部門演算子を提供します。たとえば、Haskellでは、Pythonと同様に、(1)と(2)だけを満たす_div,mod_があり、(1)と(3)だけを満たす_quot,rem_もあります。後者の演算子のペアは、負の剰余を返す代償として除算ゼロに向かってを丸めます。たとえば、_(-1) `quot` 2 = 0_と_(-1) `rem` 2 = (-1)_があります。

C#も(2)を放棄し、_%_が負の剰余を返すことを許可します。コヒーレントに、整数除算はゼロに向かって丸めます。 C99から始まるJava、Scala、Pascal、およびCもこの戦略を採用しています。

44
chi

ウィキペディアにはこれに関する素晴らしい記事があります 、歴史と理論を含みます。


言語がユークリッドの除算プロパティを満たす限り、(a/b) * b + (a%b) == a、フローリング除算と切り捨て除算の両方が一貫性があり、算術的に賢明です。


もちろん、人々は一方が明らかに正しく、他方が明らかに間違っていると主張するのが好きですが、賢明な議論よりも聖戦の性格があり、通常は何よりも初期の優先言語の選択に関係していますその他。また、主に彼らが選んだ%、おそらく/最初に%一致します。

  • フローリング(Pythonなど):
    • ドナルド・クヌースが提案するように、権威も劣らない。
    • %除数の記号に従うことは、明らかに全生徒の約70%が推測することです
    • 演算子は通常、modではなくmoduloまたはremainderとして読み込まれます。
    • 「Cはそれをする」-それは真実ではない。1
  • 切り捨て(C++など):
    • 整数除算とIEEE浮動小数点除算の整合性を高めます(デフォルトの丸めモード)。
    • より多くのCPUが実装しています。 (歴史の異なる時期に真実ではないかもしれません。)
    • 演算子はmoduloではなくremainderとして読み込まれます(実際にagainstを主張しているにもかかわらず)。
    • 除算プロパティは、概念的にはモジュラスよりも剰余に関するものです。
    • 演算子はmodではなくmoduloから読み取られるため、Fortranの区別に従う必要があります。 (これはばかげているように聞こえるかもしれませんが、C99のクリンチャーだったかもしれません。 このスレッド を参照してください。)
  • 「ユークリッド」(パスカルなど_/床に応じて、または記号に応じて切り捨てられるため、%は決して負ではありません):
    • ニクラウス・ワースは、ポジティブなmodに誰も驚かないと主張した。
    • Raymond T. Bouteは後に、ユークリッドの除算を他のルールのいずれかで単純に実装することはできないと主張しました。

多くの言語が両方を提供します。通常、Ada、Modula-2、一部のLisp、Haskell、およびJuliaと同様に、Pythonスタイルの演算子にはmod、C++スタイルの演算子にはremに関連する名前を使用します。しかし、常にではありません。たとえば、Fortranは同じものをmodulomodで呼び出します(C99について前述したとおり)。


Python、Tcl、Perl、およびその他の有力なスクリプト言語が主にフローリングを選択した理由はわかりません。質問で述べたように、Guido van Rossumの答えは、彼が3つの一貫した答えの1つを選択する必要がある理由を説明するだけであり、彼がしたものを選んだ理由ではありません。

しかし、Cの影響が重要だったと思います。ほとんどのスクリプト言語は(少なくとも最初は)Cで実装されており、Cからオペレーターインベントリを借用しています。C89の実装定義%は明らかに壊れており、TclやPythonのような「フレンドリーな」言語には適していません。 Cは演算子「mod」を呼び出します。したがって、それらは剰余ではなくモジュラスで行きます。


1.質問の内容にかかわらず、そしてそれを引数として使用している多くの人々にもかかわらず、Cは実際にはないPython and friends。C99はフロアリングではなく除算を切り捨てる必要があります。C89でもmodのいずれかのバージョンが許可され、どちらのバージョンも許可されます。壊れた。

12
abarnert

整数除算の意味は、完全な答えに剰余が含まれることだからです。

12
Paula Thomas

ポーラが言ったように、それは残りのためです。

アルゴリズムは、 ユークリッド除算 に基づいています。

Rubyでは、次のように一貫して配当を再構築できます。

puts (10/3)*3 + 10%3
#=> 10

実生活でも同じように機能します。リンゴ10個と3人。 Ok 1つをカットできますAppleは3つになりますが、設定された整数の範囲外になります。

負の数を使用すると、一貫性も維持されます。

puts (-10/3)*3 + -10%3 #=> -10
puts (10/(-3))*(-3) + 10%(-3) #=> 10
puts (-10/(-3))*(-3) + -10%(-3) #=> -10

商は常に切り捨てられ(負の軸に沿って切り捨てられます)、リマインダーは次のとおりです。

puts (-10/3) #=> -4
puts -10%3 #=> 2

puts (10/(-3)) #=> -4
puts 10%(-3) # => -2

puts (-10/(-3)) #=> 3
puts -10%(-3) #=> -1 
7
iGian