bad_int.py 技術ドキュメント

プログラムの動作

bad_int.py は、浮動小数点数を繰り返し加算した結果が、期待される整数値とわずかに異なる場合に、Pythonの int() 関数による型変換がどのように振る舞うかを示すためのプログラムです。

具体的には、コマンドライン引数で指定された浮動小数点数 \(h\)\(n\) 回加算し、その合計値 \(v\) を計算します。その後、計算された \(v\) と、微小な正の数 eps を加えた \(v + \text{eps}\) のそれぞれに対して int() 関数を適用した結果を標準出力に表示します。

これにより、浮動小数点演算の丸め誤差が int() 変換に与える影響と、その誤差を補償するための一つのアプローチを視覚的に確認できます。

原理

本プログラムは、浮動小数点数の精度と int() 変換の挙動に焦点を当てています。

多くのプログラミング言語(Pythonを含む)では、浮動小数点数はIEEE 754標準に従って表現されます。この表現方法には限界があり、一部の十進数(例: \(0.1\))は二進数で正確に表現することができません。そのため、\(0.1\)\(10\) 回加算すると、数学的には \(1.0\) になるはずですが、コンピュータ上では \(0.1\) がわずかに異なる値として表現されるため、合計値が \(0.9999999999999999\) のようになることがあります。

Pythonの int() 関数は、浮動小数点数を整数に変換する際に、小数部を切り捨て(truncate towards zero)る挙動をします。

  • int(9.999999999999999)9 を返します。

  • int(10.0)10 を返します。

このプログラムでは、計算された合計値 \(v\) が期待される整数値 \(V_{expected}\) よりもわずかに小さい場合(\(v < V_{expected}\))、int(v)\(V_{expected}-1\) となる可能性があることを示します。

計算される合計値 \(V\) は以下の数式で表されます。

\[V = \sum_{i=1}^{n} h\]

プログラムでは、この \(V\) に対して直接 int() を適用した結果と、微小な正の数 eps を加算した \(V + \text{eps}\) に対して int() を適用した結果を比較します。

\[ \text{int}(V) \quad \text{vs.} \quad \text{int}(V + \text{eps}) \]

\(V + \text{eps}\)\(V_{expected}\) 以上であれば、int(V + eps)\(V_{expected}\) を返すことが期待され、浮動小数点演算の誤差による意図しない切り捨てを防ぐ対策の一例を示します。

必要な非標準ライブラリとインストール方法

bad_int.py はPythonの標準ライブラリである sys のみを使用しており、追加の非標準ライブラリは必要ありません。そのため、特別なインストール作業は不要です。

必要な入力ファイル

このプログラムは、ファイルからの入力を必要としません。すべての入力はコマンドライン引数として与えられます。

生成される出力ファイル

このプログラムは、ファイルへの出力を生成しません。すべての結果は標準出力(コンソール)に表示されます。

コマンドラインでの使用例 (Usage)

bad_int.py を実行するには、Pythonインタープリタを使用し、引数として加算する浮動小数点数 \(h\) とその回数 \(n\) を指定します。

基本的な書式は以下の通りです。

python bad_int.py <h_value> <n_times>
  • <h_value>: 加算する浮動小数点数(例: 0.01)。省略した場合、プログラム内で定義されたデフォルト値の 0.01 が使用されます。

  • <n_times>: 加算を繰り返す回数(例: 100)。省略した場合、プログラム内で定義されたデフォルト値の 100 が使用されます。

もし引数が不足している場合、プログラムはUsageメッセージを表示して終了します。

コマンドラインでの具体的な使用例

  1. 引数不足の場合: 引数を指定せずに実行した場合、引数が不足しているためUsageメッセージが表示され、プログラムが終了します。

    python bad_int.py
    
    • 実行結果:

      Usage: python bad_if.py h n answer
          Check interger conversion of the summation of h for n times
      
  2. 標準的な例 (h=0.01, n=100): h0.01n100 を与えて実行します。数学的には合計値が 1.0 になるはずの例です。

    python bad_int.py 0.01 100
    
    • 実行結果:

      Summing up 0.01 for 100 times: v = 0.9999999999999999
      int(0.9999999999999999) = 0
      int(0.9999999999999999 + 1e-10) = 1
      
    • 説明: 0.01 を100回加算した結果 \(v\) は、浮動小数点演算の誤差により 0.9999999999999999 となっています。 この値に int() を適用すると、小数部が切り捨てられ 0 となります。 しかし、\(v\) に微小な値 eps (\(1.0 \times 10^{-10}\)) を加算すると 0.9999999999999999 + 1e-10 は実質的に 1.0 以上となるため、int() を適用した結果は 1 となります。 これは、浮動小数点数の誤差が int() 変換に与える影響と、その誤差を補償する方法を示しています。

  3. 誤差が出にくい例 (h=0.5, n=2): h0.5n2 を与えて実行します。数学的には合計値が 1.0 になる例です。

    python bad_int.py 0.5 2
    
    • 実行結果:

      Summing up 0.5 for 2 times: v = 1.0
      int(1.0) = 1
      int(1.0 + 1e-10) = 1
      
    • 説明: 0.5 は浮動小数点数で正確に表現できるため、2回加算しても 1.0 という正確な結果が得られます。 そのため、int(v)int(v + eps) の両方で期待される 1 が得られます。