端数処理
出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2024/02/04 00:44 UTC 版)
例
与えられた数値を上で挙げた端数処理によって置き換えた場合の結果を示す。この例では、丸め幅は0.1である[注 5]。
与えられた数値 | 切り捨て | 切り上げ | 四捨五入 | 五捨六入 | 偶数への丸め |
---|---|---|---|---|---|
8.05 | 8.0 | 8.1 | 8.1 | 8.0 | 8.0 |
8.051 | 8.0 | 8.1 | 8.1 | 8.0 | 8.1 |
8.15 | 8.1 | 8.2 | 8.2 | 8.1 | 8.2 |
8.25 | 8.2 | 8.3 | 8.3 | 8.2 | 8.2 |
8.263 | 8.2 | 8.3 | 8.3 | 8.3 | 8.3 |
8.347 | 8.3 | 8.4 | 8.3 | 8.3 | 8.3 |
8.35 | 8.3 | 8.4 | 8.4 | 8.3 | 8.4 |
8.45 | 8.4 | 8.5 | 8.5 | 8.4 | 8.4 |
太字の数値は、四捨五入の場合と異なる結果となるものである。
コンピュータでの丸め
低レベルの丸め
choppingは、あるビット以下を全て0にする。これは最も計算が簡単な丸めで、正の数に対しては切捨てとなる。負数に対する動作は負数の方式によるが、2の補数表現では負の無限大への丸めとなる。
choppingは、下位ビットを明示的に0にするほか、たとえば32ビットレジスタの上位16ビットを16ビットレジスタとして使うなどでも得られる。
choppingのあと、有効桁の中でのLSBをセットすると、フォン・ノイマン丸めとなる。
プログラミング言語の丸め関数
同様にビット操作で実装されるものであるが、プログラミング言語の関数などで丸めの機能が提供されている。FPUで実装されていることも多い。
通常は、丸め関数の丸め幅は1で、それ以外の丸め幅に対しては、丸め前に丸め幅で割り丸め後に丸め幅を掛ける、というのが一般的なレシピである。これは、割ったり掛けたりするのはプログラマの責任であり、処理系は「小数点以下の丸め」のみに責任を持つ、という明確な責任の分界点のあらわれである。第2引数以降で丸め幅を指定できる環境もある(が、次で述べるように問題がある)。
Ruby(やPHP)など一部の言語のライブラリでは、(十進で)小数点以下何桁目で丸める、ということを引数で指定できるものがあるが、仕様に問題がある。よく知られているように一般的な二進の浮動小数点表現では、例えばきっかり 0.1 という値は表現できない。ということは、たとえば 0.11 を小数点以下1桁に丸めた結果として 0.1 が欲しい、と要求しても、その 0.1 は内部的には「丸めた」結果とは本来は言えないものだからである。そのような計算に関する、いくつかのモデルの立て方は考えられるが[5]、いずれにしろ元々の要求のほうが無理としたほうが妥当である。
丸め関数が返す値は、小数点以下が全てゼロの値、という意味では整数だが、型は引数と同様に浮動小数点型というものも多い。これは理論的な理由よりは実際上の理由で、以前は一般的な整数型であった32ビット固定長整数で表現できる整数の範囲よりも、一般的な浮動小数点型である倍精度浮動小数点型で正確に表現できる整数の範囲のほうが広いためである。
floor, ceiling, truncate
多くの環境では、床関数(負の無限大へ)、天井関数(正の無限大へ)、切り落とし関数(0へ)が実装されている(床関数と天井関数)。それぞれの関数名には、次のようなものが使われる。
- 床関数 - floor
- 天井関数 - ceil、ceiling
- 切り落とし関数 - trunc、truncate、fix
これらは、5つのIEEE丸めモードのうちの3つの方向丸めに対応している。偶数への丸めの実装率は、これらより劣る。無限大への丸めが実装されている環境は少ない。
例: ±3.7 を丸め幅1で丸める。
- ceil(3.7) = 4, ceil(-3.7) = -3
- floor(3.7) = 3, floor(-3.7) = -4
- trunc(3.7) = 3, trunc(-3.7) = -3
round
最近接丸めは、多くの環境にroundという関数がある。しかし、どの最近接丸めかを定めている一般的となっている標準(デファクトスタンダード)は存在しないので注意が必要である。たいていは四捨五入か偶数への丸めであるが[注 6]、明示的に選択できないことも多い。
プログラミング言語 | round(0.5) | round(-0.5) |
---|---|---|
C99 C++11 Ruby |
1 | -1 |
.NET Framework Python |
0 | 0 |
Java | 1 | 0 |
JavaScript | 1 | -0 |
C言語における型変換と端数処理
浮動小数点型から整数型へのキャストなどによる型変換では、処理が単純な切り捨てになるものが多く、負の場合は実装による。
C言語のmodf関数は、実数を整数部と小数部に分割する。整数部は0への丸めである。
(以下(§a.b)のようにして示すセクションは JIS X 3010-1993(C89)のもの)
C言語およびそれと同じ仕様の言語では、キャストなどによる浮動小数点型から整数型への型変換においては、その値は小数部が捨てられる(§6.2.1.3)。よって「0への丸め」が行われる。
C89では、数学ライブラリ(§7.5)に床関数floorと天井関数ceilがあり(§7.5.6)、浮動小数点型において正方向への丸めと負方向への丸めが計算できる。
C99では、四捨五入関数round
をはじめとして、fegetround
/fesetround
(これはmath.hではなくfenv.h)による丸めモードの取得と設定など、大幅な強化が図られている。
なお、浮動小数点演算の性質上、たとえば (int)(0.6/0.2) が 3.0 ではなく 2.0 になることがある。これは、浮動小数点表現では 0.6 や 0.2 を厳密に表現できないため、0.6/0.2 が 2.9999999999999996 のような値になるためである。
テーブルメーカーのジレンマ (数表作成者のジレンマ)
この項目「端数処理」は途中まで翻訳されたものです。(原文:en:Rounding#Table-maker.27s_dilemma) 翻訳作業に協力して下さる方を求めています。ノートページや履歴、翻訳のガイドラインも参照してください。要約欄への翻訳情報の記入をお忘れなく。(2017年11月) |
ウィリアム・カハンは端数処理の(あまり意識されていなかった)難しさを示し、「テーブルメーカーのジレンマ」[注 7]というフレーズを提案した。これは「#2回以上の丸めの禁止」の節で『「もしかしたら」の部分をはっきりさせなければ、正しい丸めができない』と説明した内容の「はっきりさせる」ために必要なコストについて(実は)「オーダーを見積もる」ことすら不可能だ、という話である。カハンが指摘した後には、具体的に著しく「悪い例」としてどういう値があるか、といったサーベイ[6]などが行われている。
その一例を示しながらカハン曰く、
そこにおいてオーバーフロー・アンダーフローをしないとき正しく丸められた y^w を全ての2つの浮動小数点数の引数に対して計算するのにどれだけのコストがかかるかだれも知らない。一方、評判の良い数学ライブラリは初等超越関数を多くの場合わずかに1/2ulpを超えるのに収まりほとんど常に十分1ulpに収まるように計算する。なぜ y^w は平方根のように1/2ulpに収まるよう丸められないのだ? なぜならばどれだけの計算がかかるかだれも知らないからだ...。超越的な表現を計算して既定の桁数に正しく丸めるのにどれだけの余分な桁数を保持しなければならないかを予想する一般的な方法はない。ある有限の桁数が最終的に十分であるという(正しいとしても)事実すらも深い定理かもしれない。
この事実の帰結として、標準規格では以下のようになっている。IEEE754(1985年版)では、四則演算(加減乗除)、融合乗加算、平方根、剰余(浮動小数点剰余)については、「無限の精度で演算してそれを正しく丸めた結果」と一致することを要求し、また規格に合致していると保証する実装ではそのことを保証しなければならない。一方で、より複雑な関数(演算)に対しては1985年版の仕様では同様な要求は示されず、それらに対しては典型的には「最終bitの範囲内(いわゆる「1ULP」)」の正しさは保証され無い。2008年版ではいくつかの更新があった。
Gelfond–Schneider理論およびLindemann–Weierstrass理論を用いることにより、標準の初等関数の多くは非零の有理数の引数に対して結果が超越的になる(有限回の代数演算では表せない)ことが証明されている。そのような関数の値を正しく丸めることは(原理的には)常に可能であるが、正しく丸められた値を導くために途中の計算をどれくらい高い精度で行う必要があるかの限界を事前に決めることにも多くの計算時間を必要とするかもしれない。
いくつかのパッケージは正しい丸めを提供する。
- GNU MPFRパッケージは正しく丸められた任意精度の結果を与える。
他のいくつかのパッケージは倍精度において正しい丸めの初等関数を実装している。
- IBMのlibultim (最近接丸めのみ)
- Sun Microsystemsのlibmcr (4つの丸めモードについて)
- Arénaireチーム(LIP, ENS Lyon)によるCRlibm (4つの丸めモードをサポートし、それは証明されている。)
それについて丸められた値がどれだけの桁を計算してもdeterminedになりえないような計算可能な数が存在する。特定のインスタンスは与えられることはないが、存在は停止問題の決定不能性から導かれる。 たとえば、もしも「ゴールドバッハの予想(4以上の任意の正の偶数は必ず2つの素数の和で表せる)」が真であって、しかし証明不可能な命題であると仮定すれば、次の式の値を(切り上げて)整数に丸めた結果を決定することはできない。
10^−n ここでnは4より大きい偶数で2つの素数の和にはならない最小のもの、あるいはもしそのような偶数が無ければ0とする
丸めた結果はもしそのような偶数nが存在すれば1、存在しなければ0である。しかし「予想」が証明不可能であっても丸められる前の値であれば与えられた任意の精度で近似できる。
- ^ 「最近接偶数への丸め」、「偶数丸め」、「最近接丸め」、「JIS丸め」、「ISO丸め」、「銀行家の丸め」、「銀行丸め」、「五捨五入」、「偶捨奇入」という用語を採用している文献は、現在のところ発見できていない。詳細は、ノートを参照。
- ^ 負の数の場合も含めた明示的表現としては、そうなる。
- ^ もちろんこれは記数法に依存した名称である。本文では十進法における性質を説明しているが、十二進法において「五捨六入」と呼ばれるであろう処理は通常の半数切り上げである。
- ^ 一般に関数の数値計算の場合、...000 のように 0 が続いていても、下の桁で上の桁からの桁借りが発生するかもしれない。また lexer によるリテラルの読込みの場合、浮動小数点数の表現として本来ありえない桁まで記述されている、
XXX...XXX.5000000000000000000000001
といったような場合の下の桁の扱いをどうすべきか、といった点も問題になる(前述のようなちょうど境界だった場合、下の桁は必ずしも無意味とは言えないかもしれない)。 - ^ JIS Z8401:2019, p.2, 2 e) 「数値を示す場合、常に丸めの幅を示すことが望ましい。」
- ^ 偶数への丸めが推奨されてはおり、徐々に標準となってゆくと思われる[要出典]。Microsoftの一部の環境など、仕様で明示しているものもある。しかし、C99もC++11もJavaもECMAScript(JavaScript)も異なるルールを仕様に定めている。
- ^ このフレーズに含まれる「テーブルメーカー」とは、「数表」を計算し、それを出版せんと企てる者、という意味である。数表は一般に、それに印刷されている桁数の範囲内は必ず正しいものでなければならないことが要求される(であろう)という背景がある。例えば、上限と下限の両方を計算することで結果がある範囲内に必ずあることを保証するといったような手法が、数表の正確さのために活用されてきた、という歴史がある。
- 端数処理のページへのリンク