UNIX系OSでの、 浮動小数点演算の丸めについて

たけおか

初出: 2010/NOV/20
update: 2010/NOV/23 FreeBSD6に関してと、おまけを追記


0.はじめに

浮動小数点演算を行うにあたって、ユーザ(プログラマ)は、精度を管理しなければならない。

丸めは重要な要素である。
浮動小数点演算を行うごとに、その結果を丸める。

IEEE 754(通称 IEEE Float)は、丸めの方法を制御可能としている。
また、言語処理系 C99では、丸めの指定方法を規定している。
ただし、ハードウェアの制約などで指定や変更が、不可能な場合もある。

ここでは、MacOSX(PPC), x86のFreeBSD, NetBSD, Linuxや C99(ISO C言語規格)の 丸め指定方法について記述している

また、末尾におまけとして、ソフトウェアによる四捨五入の方法を記述している。


1. UNIX系OSの丸め指定

UNIX系OSでは、丸めの指定が可能なものが多い。
x86のFreeBSD, NetBSD, LinuxやMacOSXは制御可能

FreeBSD 4.7, NetBSD 1.6.1, MacOSX, Linux2.6.11 の丸めのデフォールトは、
'Nearest'(四捨五入)
である。



1.1 FreeBSD 4.7, NetBSD 1.6.1 の丸め指定

これは、すでに古くなっているかも知れない。
その場合は、次項の C99 の指定方法を、試してみるとよい。
FreeBSD 6 では、次項の C99 方式になっている。

FreeBSD 4.7-STABLE #5の /usr/include/machine/ieeefp.h 中に、
/*
 * FP rounding modes
 */
typedef enum {
        FP_RN=0,        /* round to nearest */
        FP_RM,          /* round down to minus infinity */
        FP_RP,          /* round up to plus infinity */
        FP_RZ           /* truncate */
} fp_rnd_t;
 
という定義がある。

これを使用して、丸めの指定を行うには、
Cソース中で次の関数を使用する。
#include <ieeefp.h>

fpsetround(FP_RN) /* 一番近い数に丸める */
fpsetround(FP_RM) /* マイナス無限大方向に丸める */
fpsetround(FP_RP) /* プラス無限大方向に丸める */
fpsetround(FP_RZ) /* 小数部を0にする (truncate) */

fpgetround() /* 設定状態が得られる */
 


1.2 C99, MacOSX, FreeBSD_6, Linux の丸め指定

MacOSXの /usr/include/architecture/ppc/fenv.h 中に、
/*    Definitions of rounding direction macros                                */
enum {
  FE_TONEAREST                  = 0x00000000,
  FE_TOWARDZERO                 = 0x00000001,
  FE_UPWARD                     = 0x00000002,
  FE_DOWNWARD                   = 0x00000003
};
 
が定義されている。
これを使用して、丸めの指定を行うには、
Cソース中で次の関数を使用する。
#include <fenv.h>

fesetround(FE_TONEAREST) /* 一番近い数に丸める */
fesetround(FE_UPWARD) /* プラス無限大方向に丸める */
fesetround(FE_DOWNWARD) /* マイナス無限大方向に丸める */
fesetround(FE_TOWARDZERO ) /* 小数部を0にする (truncate) */

fegetround() /* 設定状態が得られる */
 


2. 丸め指定の効果

product()
{
 int i, n;
 double prod;
 double tmp;

 n = ND;

 prod = 0.0;
 for(i=0; i < n; i++){
   tmp = A[i]*B[i];
   prod += tmp;
 }
 printf("product  = %25.20f\n", prod);
}
を実行。
データは、真値が 0.5 であるものを使用。

x86の NetBSD, FreeBSD, Linux の全てで、同じ結果。
(確か、PowerPCのMacOSXでも同じ結果だと思うが、忘れてしまった)
Near:0.50000000000000011102
Up:  0.50000000000000766054
Down:0.49999999999999328315
Chop:0.49999999999999977796
 



3. まとめ

数値計算では、誤差の管理、丸めの管理も重要である。

組込み系CPUでは、丸め方法が固定であることも多い。
組込みCPU用のソフトウェアのテスト用データを、x86の計算機で作成する場合、 x86と対象CPUの差異をよく認識して作業を行わねば、 作成したデータが無意味となることもある。
特に、 x86 の精度80bit問題 は、頭に置いておかねばならない。


それにしても…

丸めの変更が動的にできるということは…

コンパイラで定数の最適化は不可能、もしくは現実的ではないでは?
コンパイル時には、どこで、どの丸め方式が使用されるか、知り得ない。

FORTRANのような静的な言語が望まれる
(UNIX系OS下では、Fortranでも、動的に丸め方式の変更が可能ではあるが (^^; )

おまけ

古来より定石(常識)である、
浮動小数点数を整数化するときの切上げ、切り捨て、四捨五入のテクニックを 書く。
これは、浮動小数点数を整数化する時に、「切り捨て」であることが前提である。
Fortran や C で普通に使われるテクニックである。
これは、昭和の人には、死ぬほど常識である。


 int i;
 double a;

 i = a; /* 切り捨て */
 i = a + 0.5; /* 四捨五入 */
 i = a + 0.9; /* 切上げ */
 
これを少し応用すれば、小数点以下2桁にする四捨五入(小数3桁目を四捨五入)も できる。
 int i;
 double a;

 a=a*100;
 i = a + 0.5; /* 四捨五入 */
 a=i/100.0;
 
あまりに表現の限界に近い場合は、精度が失われるかも知れないが、 計算機が出現した古来より、普通に使われる定石である。


たけおか(竹岡尚三)のホームページ