1. ホーム
  2. python

[解決済み] Pythonが整数の除算を負の無限大に丸めるのは、数学的にどういう理由からですか?

2023-05-23 19:25:43

質問

Pythonを知っています // は負の無限大に向かって丸くなり、C++では / は切り捨てで、0に丸めます。

と、ここまでで分かっているのはこんな感じです。

               |remainder|
-12 / 10  = -1,   - 2      // C++
-12 // 10 = -2,   + 8      # Python

12 / -10  = -1,     2      // C++
12 // -10 = -2,   - 8      # Python

12 / 10  = 1,      2       // Both
12 // 10 = 1,      2

-12 / -10 = 1,    - 2      // Both
          = 2,    + 8

C++:
1. m%(-n) == m%n
2. -m%n == -(m%n)
3. (m/n)*n + m%n == m

Python:
1. m%(-n) == -8 == -(-m%n)
2. (m//n)*n + m%n == m

しかし、なぜPythonの // は負の無限大の方向に丸めるのでしょうか?それを説明する資料は見つからず、ただ漠然と言われるのを見つけたり聞いたりしていました。 数学的な理由によるものです。 .

例えば C++では-1/2は0に評価されますが、Pythonでは-1に評価されるのはなぜですか? :

これらのことを抽象的に扱う人は、次のように感じる傾向があります。 負の無限大に丸めるのがより理にかなっている ( というのは 数学で定義されているモジュロ関数と互換性があることを意味します。 が多少おかしな意味を持つよりも ).

しかし、私はC++の's / はモジュロ関数と互換性がありません。C++では (m/n)*n + m%n == m も適用されます。

では、Pythonが負の無限大に丸めることを選択する(数学的)理由は何でしょうか?


以下も参照してください。 この話題に関する Guido van Rossum の古いブログ記事 .

どのように解決するのですか?

<ブロッククオート

しかし、なぜPythonなのか // は負の無限大に向かって丸めることを選択したのでしょうか?

私は、もし

を選択した理由がどこかに記録されているかどうかはわかりませんが (私の知る限り、どこかの PEP で非常に長く説明されているかもしれません)、それが理にかなっているさまざまな理由を思いつくことができるのは確かです。

1 つの理由は、単純に、負の (または正の!) 無限大に丸めることは、すべての数字が同じように丸められることを意味しますが、ゼロに丸めることはゼロを特別なものにします。 これを数学的に言うと、-∞に向かって丸めることは 並進不変 つまり、方程式を満たすということです。

round_down(x + k) == round_down(x) + k

すべての実数に対して x とすべての整数に対して k . ゼロに向かって丸めることは、例えば、以来、そうではありません。

round_to_zero(0.5 - 1) != round_to_zero(0.5) - 1

もちろん、他の議論も存在します。例えば、あなたが引用している、(私たちが望む) % 演算子との互換性 (どのように動作させたいか) に基づく議論などです。

確かに、私は は、なぜ Python の int() 関数は ではなく は浮動小数点数の引数を負の無限大の方向に丸めるように定義されているので m // n と同じになるように int(m / n) . (歴史的な理由と思われます。) また、Python には少なくとも math.floor() を満たす m // n == math.floor(m / n) .


しかし、C++の / はモジュロ関数と互換性がありません。C++では (m/n)*n + m%n == m も適用されます。

その通りですが、そのアイデンティティを保持しながら / がゼロに向かって丸くなるように定義する必要があります。 % を負の数に対して厄介な方法で定義する必要があります。 特に、以下のPythonの有用な数学的特性の両方を失います。 % :

  1. 0 <= m % n < n すべて m であり、すべての正の n であり
  2. (m + k * n) % n == m % n すべての整数に対して m , nk .

の主な用途の1つは、これらのプロパティが有用であることです。 % の主な用途の 1 つは、数値の周りを囲むことです。 m を限られた長さの範囲に n .


例えば、方向を計算しようとする場合、次のようにします。 heading が現在の コンパスヘディング で、度数(真北から時計回りにカウント。 0 <= heading < 360 を使って時計回りに数える)、そして angle 度(ここで angle > 0 は時計回りに回す場合、または angle < 0 である。) Pythonの % 演算子を使うと、新しい方位を簡単に計算することができます。

heading = (heading + angle) % 360

であり、これは単純にすべてのケースで動作します。

しかし、この式をC++で使おうとすると、丸め方のルールが異なり、それに応じて % 演算子を使用すると、折り返しが常に期待どおりに機能するとは限らないことがわかります! たとえば、北西に向かってスタートする場合 ( heading = 315 ) で始まり、時計回りに 90° 回転したとします ( angle = 90 ) をすると、確かに北東を向くことになります ( heading = 45 ). しかし、そのとき 引き返そうとする 反時計回りに90°回転して ( angle = -90 ) を、C++ の % 演算子を使えば、最終的に heading = 315 には戻らず、代わりに heading = -45 !

正しい回り込み動作をさせるために、C++の % 演算子を用いて正しい折り返し動作を得るには、代わりに次のような式を書く必要があります。

heading = (heading + angle) % 360;
if (heading < 0) heading += 360;

として、あるいは

heading = ((heading + angle) % 360) + 360) % 360;

(もっと簡単な式 heading = (heading + angle + 360) % 360 が常に有効であることが保証されている場合のみ有効です。 heading + angle >= -360 .)

これは割り算の丸めルールが翻訳非可変であることの代償であり、その結果、翻訳非可変の % 演算子の代償です。