端数処理

出典: フリー百科事典『ウィキペディア(Wikipedia)』
Truncate関数から転送)
移動先: 案内検索

端数処理(はすうしょり)または丸め(まるめ)とは、与えられた数値を、ある一定の丸め幅の整数倍の数値に置き換えることである。常用的には、10累乗(…、100、10、1、0.1、0.01、…)が丸め幅とされることが多い。

丸めの種類

凡例

丸めは任意の丸め幅に対し可能だが、以下では特にことわらない限り、丸め幅を1とする。任意の丸め幅で丸めるには、丸める前に丸め幅で割り、丸めた後に丸め幅をかければいい。

記数法が問題になるときには、10進法で表されているものとする。

主に正の数について述べるが、適宜、負の数についても述べる。

切り捨て・切り上げ

整数部分をそのまま残し、小数点以下を0とする丸めを切り捨てという。それに対し、小数点以下が0でなかった場合整数部分を1増やし、小数点以下を0とする丸めを切り上げという。

負の数を考えると、切り捨て・切り上げに準ずる丸めは、4種類ある。それぞれ「○○への丸め」と呼ばれる。

符号を無視して絶対値を丸める場合、切り捨ては、常に0へ近づく(または変わらない。以下では省略)ので「0への丸め (rounding toward zero; RZ)」、切り上げは、常に数直線上の無限遠点へ近づくので、「無限大への丸め (rounding toward infinity; RI)」と呼ばれる。単に切り捨て・切り上げというと、これらをさす。

逆に、正の数の場合と増減を同じ向きにする場合は、切り捨ては、常に減るので「負の無限大への丸め (rounding toward minus infinity; RM)」、切り上げは、常に増えるので「正の無限大への丸め (rounding toward plus infinity; RP)」と呼ばれる。

切り捨て・切り上げは、最も計算が単純な丸めである。その一方で、丸め誤差上界が1(最大が1近い)と大きい。さらに悪いことに、誤差が常に同じ符号であるというバイアスがあり、丸めた数を多数足し合わせると、個数に比例して丸め誤差が累積する。この欠点のため、限られた目的にしか使われない。

数値が増えては(あるいは減っては)困る場合は、切り捨て(あるいは切り上げ)が使われる。

  • 安全基準は、常に安全な方に丸められる。
  • 誤差不確かさは、切り上げられる。
  • 数値が実際より増えると誇張・虚偽・捏造とみなされるおそれがあるときは、切り捨てられる。

(広義の)最近接丸め

丸め誤差を小さく抑えるには、常に最も近い整数(2つある場合はそのうちどちらか)に丸めればいい。これを「最近接丸め (round to the nearest)」という。ただし、単に「最近接丸め」というと、後述する「最近接偶数への丸め」を意味することが多いので注意。

最近接丸めでは、丸め誤差は最大0.5と、切り捨て・切り上げの半分になる。バイアスも、端数がランダム([0,1)で一様分布)の場合は発生しない。端数がランダムでなく端数0.5が有限の割合で発生する場合のみバイアスが発生するが、それでも、切り上げ・切り捨てより格段に少ない(端数が全て0.5のデータを四捨五入するといったワーストケースでは同じ程度になる)。

端数がちょうど0.5だった場合どちらに丸めるかで、いくつかの変種がある。

四捨五入

端数が0.5未満なら切り捨て、0.5以上なら切り上げる丸めを「四捨五入」という。JIS Z 8401で規則Bとして定められている。「四捨五入」という呼び名は、小数第1位が4以下ならば切り捨て、5 以上ならば切り上げることに相当することから来ている。この呼称は10進法での表現に依存しているので、10進法に限定しないときはR丸めという。

正の数に対しては、0.5を足して切り捨てるという、単純なアルゴリズムで得られる。なお、負の数に対して正常な結果を得ようとすれば、切り捨ては負の無限大への丸めである必要がある(0への丸めだと、たとえば−2が−1に丸められてしまう)。ただし、0.5を足して負への無限大へ丸めると、端数が0.5の場合に絶対値が減る(たとえば、−1.5は−1へと丸められる)。一方、JIS Z 8401では、負の数は絶対値として丸める(−1.5は−2へと丸められる)。実際に、コンピュータで負の数に「0.5を足して切り捨て」た場合どうなるかは、負の数と切り捨ての実装による。

端数が0.5のとき常に増える方向に丸められるため、(端数がランダムでない場合は)わずかに正のバイアスが発生しうる。

五捨五超入

端数が0.5以下なら切り捨て、0.5超なら切り上げる丸めを「五捨五超入」という。

0.5は常に切り上げられるという、四捨五入とは逆の特徴を持つ。端数がランダムでない場合は、わずかに負のバイアスが発生しうる。

正の数に対しては、0.5を引いて切り上げることで得られる。

最近接偶数への丸め

最近接偶数への丸め (round to the nearest even; RN) は、端数が0.5より小さいなら切り捨て、端数が0.5より大きいならは切り上げ、端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める。JIS Z 8401で規則Aとして定められていて、規則B(四捨五入)より「望ましい」とされている。

四捨五入ではバイアスが発生する、端数0.5のデータが有限割合で存在する場合でも、バイアスがないのが特徴であり、多数足し合わせても丸め誤差が特定の側に偏って累積することがない(偶数+0.5は現れるが奇数+0.5は現れない、といったような特徴があるデータであれば、やはりバイアスはあらわれる)。

単に「偶数丸め」「最近接丸め」とも呼ばれる。JISで定められていることから「JIS丸め」、あるいは同様にISO-31で定められていることから「ISO丸め」ともいう。英語では、誤差の累積を嫌い銀行家が好んで使ったため「銀行家の丸め (bankers’ rounding)」ともいう。5が切り捨てられたり切り上げられたりするので「五捨五入」と呼ばれたり、偶数の上が切り捨てられ奇数の上が切り上げられるので「偶捨奇入」と呼ばれたりもする。

IEEE丸め

IEEE 754で丸めモードとして定められている

  • 最近接偶数への丸め
  • 0への丸め
  • 正の無限大への丸め
  • 負の無限大への丸め

の4つを「IEEE丸め」と総称する。

実用上は最近接丸めとなる丸め

定義は最近接丸めになっていないが、最近接丸めと等しくなる場合にのみ実用される丸めがいくつかある。

五捨六入

小数第1位が5以下ならば切り捨て、6以上ならば切り上げる丸めを「五捨六入」という。

0.4を足して切り捨てることで得られる。0.55が0へ丸められることから、五捨六入が最近接丸めではないことがわかる。端数がランダムなデータに対しは、やや強い負のバイアスがあるため、そのようなデータに対し五捨六入が使われることはまずない。

五捨六入が実用的なのは、端数が0.1の整数倍のみを取りうる場合に限られる。この場合の五捨六入は、0.1~0.5で切り捨て、0.6~0.9で切り上げ(0.5超0.6未満は発生しない)なので、最近接丸めの一種の五捨五超入と同じ結果となる。

例えば、麻雀のとあるローカルルールでは、最終的な得失点を五捨六入する。この場合の端数は常に0.1の整数倍(100の倍数を、1000の倍数に丸める)なので、丸め結果は五捨五超入である。

コンピュータでは、プロセッサによっては四捨五入と五捨六入を均等に使い分け、バイアスを0にする工夫がなされているものがある。

四捨六入

アルシーアル麻雀得点計算では、かつて端数処理が行われる前の段階の計算による得点を丸めるときに四捨六入と呼ばれるものが採用されていた。これは丸める桁が必ず偶数になるためであり、実質的には最近接丸めである。

スウェディッシュ・ラウンディング

ニュージーランドでは現金での支払いの際、スウェディッシュ・ラウンディングと呼ばれる特殊な方法で端数処理が行われている。

丸め幅5で五捨六入することに等しい。つまり、(5を単位とした)端数が3未満なら切り捨て、3以上なら切り上げとなる。

端数がランダムな場合は、五捨六入と同様に非実用的である。しかし通常は、1刻みのデータに対し5を丸め幅として丸めるので、その結果は最近接値への丸めである。

特殊な丸め

乱数丸め

[0,1)の一様乱数を発生させ、端数以上なら切り捨て、端数より小さければ切り上げる。

一様乱数を足して切り捨てることで得られる。

丸め誤差は上界が1だが、分布が0近くに集まっているため、ランダムなデータに対する平均二乗誤差は切り捨て・切り上げよりは少ない。

任意の分布の端数に対して、バイアスがないのが特長である。たとえば、0~0.5の間に端数が多かったとすると、最近接偶数への丸めでは負のバイアスが生まれるが、乱数丸めではバイアスがない。

ディザの一種として使われる。

フォン・ノイマン丸め

常に奇数側へ丸める。

2進法では、切り捨てた後LSBをセットするという、簡単なアルゴリズムで得られる。

丸め誤差は切り捨て・切り上げと同程度で大きいが、ランダムなデータに対してはバイアスがないという性質は持っている。

2回以上の丸めの禁止

同じ数値を2回以上丸めてはいけない。たとえば、122.51を最近接偶数へ丸めるときに、まず122.5とし、次に122とすると、結果が違ってしまう。

以下の数値を上であげたような端数処理により有効数字 2 桁にする場合を考える。

もとの数値 切り捨て 切り上げ 四捨五入 五捨六入 偶数丸め
8.05 8.0 8.1 8.1 8.0 8.0
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.26 8.2 8.3 8.3 8.3 8.3
8.34 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引数以降で丸め幅を指定できる環境もある。

丸め関数が返す値は、小数点以下が全てゼロの値、という意味では整数だが、は引数と同様に浮動小数点型というものも多い。これは、浮動小数点型としては一般的な倍精度浮動小数点型の仮数部が50ビット前後あるのに対し、固定長の整数型は昔は大きいものでも32ビットだったため、入りきらない値の可能性もあり不便だったためである。64ビットの整数型も昨今は標準化されるなどしているが、互換性の問題もあり過去の仕様が継承されている。

floor ceil trunc

ファイル:Ceilfloor nt.png
floor関数(緑)とceil関数(赤)

多くの環境では、床関数(負の無限大へ)、天井関数(正の無限大へ)、切り落とし関数(0へ)が実装されている(床関数と天井関数)。それぞれの関数名には、次のようなものが使われる。

  • 床関数 - floor
  • 天井関数 - ceilceiling
  • 切り落とし関数 - trunctruncatefix

これらは、4つのIEEE丸めモードのうち3つに対応している。4つ目の最近接偶数への丸めの実装率は、これらより劣る。IEEE丸めに含まれない無限大への丸めが実装されている環境は少ない。

例: ±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

Microsoft Excelceiling 関数は、その名に反して、無限大への丸め(正負によらず絶対値が大きい方への丸め)をおこなう。例えば、ceiling(-4.5,1) であれば −5 となる。

その他の丸め関数

浮動小数点型から整数型へのキャストなどによる型変換では、処理が単純な切り捨てになるものが多く、負の場合は実装による。

C言語modf関数は、実数を整数部と小数部に分割する。整数部は0への丸めである。

多くの環境にroundという関数があるが、環境によって動作が異なるので、使用には注意が必要である。一般的には、四捨五入か最近接偶数への丸めをすることが多い。

C言語における型変換と端数処理

(以下(§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 のような値になるためである。

関連項目