コンピュータは数値を正確に扱えない

(更新日: 2017/04/03)

コンピュータで数値を正確に扱うのは「実は結構難しい」です。つまり「コンピューターは数値を正確に扱えない」という事です。「コンピューターが数値を正確に扱えない?!何を言ってるんだ?!」と思った方は是非読んでみてください。

コンピューターは数値を正確に取り扱えない

コンピューターは数値を正しく取り扱えるから便利なのでは?と思うかも知れません。「コンピューターは数値を正確に取り扱えない」これは動かし難い事実/制限です。

コンピューターが数値を正しく取り扱える条件が決まっています。条件の範囲外であれば正確に取り扱えません。

整数

最も解りやすいのは整数です。通常、整数は固定の記憶領域を持つ整数型で表現されます。32ビット整数、64ビット整数、という言葉はITシステムの開発者でなくても聞いた事があると思います。これらの整数には符号付きと符号無し整数があります。正確に表現できる整数は以下の通りです。

  • 符号付き32ビット整数: -2^31 〜 2^31 – 1
  • 符号無し32ビット整数: 0 〜2^32
  • 符号付き64ビット整数: -2^63 〜 2^63 – 1
  • 符号無し64ビット整数: 0 〜2^64

範囲を超えた場合、数値は正しく保存できません。下限を超えた場合はアンダーフロー、上限を超えた場合はオーバーフローが発生します。オーバー/アンダーフローした値は正確に表現できません。

例えばPHPの場合、整数がオーバーフローすると自動的により表現可能な範囲が広い浮動小数点型に変換します。

しかし、64ビット環境ではこの動作による数値の値は正確ではありません。

実数(浮動小数点)

コンピューターによる浮動小数点型の数値演算で最初の方に学ぶことは「浮動小数点演算は正確でない」ことです。一般に浮動小数点演算を行う場合、IEEE 754の倍精度浮動小数点型(いわゆるdouble型)を利用します。

倍精度浮動小数点型が正確に数値を表現できる数値の有効数字の範囲は符号付き53ビット整数分しかありません。符号付き64ビット整数を浮動小数点型に変換した場合、12ビット分の情報が失われます。つまり、正確ではない、のです。

浮動小数点型は 1/3 (= 0.3333333333333333….) などの数値は正確に表現できません。有効数字の範囲内で”できるだけ正確”に保存します。”できるだけ正確”に保存しても、実際の正確な数値は保存されません。このような数値は必ず”丸められる”ことになります。

丸められた数値には”誤差”あります。丸められた数値を使って演算すると”誤差”がどんどん積み重なります。倍精度浮動小数点型の有効数字は符号付き2^52の範囲ですが、演算をすると有効桁数がたった数桁になってしまう(正確に表現できている演算結果の桁数が数桁になる)といったことがごく普通にあります。

 

実際のアプリケーションにおける数値の取り扱い

コンピューターが数値を正確に取り扱えないことは解りました。浮動小数点型はどうしようもないです。各自(開発者)ができるだけ演算が正確になるよう努力するしかありません。

整数が正確に取り扱えないならエラーにすれば良い、と考えるでしょう。データを正確に保存する役割をもつRDBMSのデータ型のキャスト(文字リテラルから整数型)でどのような動作になるか確認してみます。

PostgreSQLとMariaDB/MySQLの整数の取り扱い

PostgreSQLのint8型のオーバーフローはエラーになります。

MariaDBのint型のキャストはエラーとならず、オーバーフローした値になります。データを保存した場合、オーバーフローした値が保存されます。1

 

整数オーバー/アンダーフローをどう取り扱うべきか?

RDBMSの場合、整数のオーバー/アンダーフローがエラーとなって実行できなくても問題ありません2。通常のアプリケーション(Webアプリなどコード)で発生した場合にエラーになってプログラムが停止しては困ります。

例えば、

の実行結果が整数オーバー/アンダーフローしたからといって、ゼロ除算エラーのようにいきなりプログラムの実行が停止すると困ります。Webアプリなら真っ白なページになったりします。Excel/Wordのようなアプリの場合、セーブする間も無くクラッシュすることになります。

「ゼロ除算エラーのようにいきなりプログラムの実行が停止すると困る」ということは「言語レベルで整数オーバー/アンダーフローをエラーにできない」ということです。3

 

整数オーバー/アンダーフローを防ぐのはアプリ開発者の責任

「MariaDB/MySQLがデフォルトで整数オーバーフローをエラーにしないのはMariaDB/MySQLが悪い!」と思うかも知れません。しかし、これは半分当たりですが半分外れです。

RDBMSに限らず、整数オーバーフローはどこにでも発生し得ます。外部システムやライブラリの中でなく、アプリ開発者が書いているコードにも発生し得ます。

誰の責任に於て整数オーバー/アンダーフローを防止/検出すべきか?

RDBMSやライブラリなどが整数オーバー/アンダーフローを検出してくれるかも知れませんが、それでは手遅れの場合もあります。さらに整数オーバー/アンダーフローはアプリ開発者が書いているコードにも発生し得えます。

従って、アプリ開発者が責任を持つべきであることになります。4

 

アプリ開発者はどう責任を果すべきか?

一般的な数値型のデータの場合、普通に数値範囲や数値桁数を入力バリデーションしていればオーバー/アンダーフローは起こりえないケースが多数あります。

入力バリデーションだけではオーバー/アンダーフローを防止できない場合、アプリケーションプログラマの責任に於て、オーバー/アンダーフローを検出しなければなりません。

参考: PHPで整数オーバーフロー/アンダーフローをチェックする方法

 

使っている言語は任意精度の整数だから大丈夫?

いいえ、大丈夫でないことは明らかです。データベースに不正な情報が保存されても構わない、というシステムは多くないでしょう。言語の整数型は任意精度整数でも、外部システムやライブラリの整数型が任意精度であることの方が少ないです。

 

まとめ

コンピューターが最も得意とする分野が数値の取り扱いですが、その最も得意とする分野でさえ「正確」に取り扱えません。

しかも、ライブラリや基盤となるソフトウェアにはオーバーフローを敢えてチェックしていないモノも多数あります。

さらに、単純におかしなデータになるだけでなく整数演算のバグがセキュリティ脆弱性の原因になっているモノも多数あります。

Webアプリに限らず、数値パラメーターが「数値である」ことだけを入力バリデーションしている「弱い入力バリデーション」であること5が多いです。ISO 27000等のセキュリティ標準が求める入力バリデーションはこのような「弱い入力バリデーション」ではありません

コンピューターは数値を正確に扱えません。数値の入力データが正しく取り扱えるよう「強いバリデーション」を使うようにしましょう。

最近は安易にデータベースにNoSQLを使っているアプリが増えています。RDBMSを使い、参照整合性を定義していれば”不正なID”でアプリが使い物にならなくなる(DoSに脆弱になる)ことはありませんが、安易にNoSQLを使っているアプリの場合、極端にDoSに脆弱になることがあります

仕組み的にコンピューターは数値を正確に扱えません。基本的なことですが忘れがちなので注意しましょう。

参考

 


  1. MariaDB/MySQLの場合、SQLモードをTRADITIONALにすると、PostgreSQLと同様にエラーにできます。ただし、SQLモードをTRADITIONALにするとデフォルト状態のMariaDB/MySQL用に書かれたコードが多数動作しなくなります。 
  2. データベースのクエリ/トランザクションは、そもそも失敗すること、を前提として利用するものです。 
  3. オーバー/アンダーフローを実行時の例外にするのはあり得ます。 
  4.  RDBMSやライブラリが整数オーバー/アンダーフローを検出してエラーにすることは無意味ではありません。多層的にバグや脆弱性を防止するのは必要なことです。 
  5. もっと酷いケースは数値範囲が明白にも関わらず、バリデーションせず数値に強制キャストするだけの場合もあります。 

Comments

comments