十進整数の偶数丸めと2進数の偶数丸め

voltage

制御装置のファームウェアなどで整数の値を丸める必要があるときに、 切捨てや四捨五入で丸めると誤差の蓄積が問題になる場合があります。
例えば、16ビットの測定データの結果を十進数で 3桁に丸めて -99.9〜+99.9 の値と して扱い、それを積算に用いるような場合が考えられます。
そのような場合は、JIS Z 8401 規則A にならって偶数丸めを行う方が良いです。
また、2進整数の右シフトによる安易な2の冪での除算は切り捨てと同様な誤差の蓄積をもたらします。 この場合も偶数丸めと同様な考え方で扱うのが好ましいです。

十進数の偶数丸め

●十進数の偶数丸めと、四捨五入による誤差の蓄積の例
誤差の例として、入力が 20〜0 へと順次変化した場合の丸めの例を list.1 に示します。
入力を積算した場合の値 210 に対して 偶数丸めの場合には 210 と誤差が少なく(この場合は零に)なっているが、 四捨五入での積算値は 220 と積算値が5%大きくなっています。
つまり四捨五入は下位桁がその上の桁の半分の大きさのときに常に下位桁の半分だけ少なく丸めているとも言えます。
このことによる統計的な誤差を軽減するために、下位桁がその上の桁の半分の大きさのときには上位桁が偶数に なるように丸めたのが偶数丸めです。
list.1 丸めの例
入 力   四捨五入 偶数丸め
i =  20  j =  20  k =  20
i =  19  j =  20  k =  20
i =  18  j =  20  k =  20
i =  17  j =  20  k =  20
i =  16  j =  20  k =  20
i =  15  j =  20  k =  20
i =  14  j =  10  k =  10
i =  13  j =  10  k =  10
i =  12  j =  10  k =  10
i =  11  j =  10  k =  10
i =  10  j =  10  k =  10
i =   9  j =  10  k =  10
i =   8  j =  10  k =  10
i =   7  j =  10  k =  10
i =   6  j =  10  k =  10
i =   5  j =  10  k =   0
i =   4  j =   0  k =   0
i =   3  j =   0  k =   0
i =   2  j =   0  k =   0
i =   1  j =   0  k =   0
i =   0  j =   0  k =   0
-------------------------
計  210      220      210
偶数丸めでも値が変化範囲が小さい場合など下位2桁が十分ランダムでは無い時には積算誤差が大きくなる場合があります。
数値的な再現性が不要であれば、そのような場合に下位桁と同じ大きさで有界な一様乱数を足してから切捨てる乱数丸めを使う事があります。

●プログラム例
偶数丸めのプログラム例を list.2 に示します。
符号無し整数の場合には、負の数の条件分け部分 (list.2 中の青色の箇所) が省略できます。
丸めと共にそのまま桁を一桁減らす場合には、リターン文のところ (list.2 中の茶色の箇所) を単に
return x / 10; 
とすれば良いです。
list.2 整数の偶数丸めのプログラム例 (C言語の場合)
/* NAME
 *    _rnd2evn - round least significant digit
 * SYNOPSIS
 *    int rnd2evn(int x)
 * DISCRIPTIONS
 *    The rnd2evn() function return the nearest multiple-of-ten value of x.
 * REFERENCE
 *    ISO 31-0:1992, Quantities and units - Part0 : General principles, 
 *                  Annex B (Guide to the rounding of numbers)
 * SEE ALSO
 *    JIS Z 8401
 * COPYRIGHT
 *    Copyright (c) 2009, Takayuki HOSODA. All rights reserved.
 */

int rnd2evn(int x) {
    if (x > 0) {
        x += 4 + (1 & (x / 10));
    }else{
        x -= 4 + (1 & (x / 10));
    }
    return x - (x % 10);
}
●補足
偶数丸めの仕方として、先に上位桁の半分の値 5を足して、それを上位桁の倍の値 20で割った余りが 丁度上位桁の大きさと同じ 10になる時には 1を減じるという考えもありますが、
それを list.3 のようにそのままプログラムした場合には、list.1 に比べ条件分岐が一つ無駄に増えるのがあまり芳しくなく思えます。

list.3 正の整数の偶数丸めのプログラム例 (非推奨, C言語の場合)
int urnd2evn(int x) {  
    x += 5;
    if (x % 20 == 10)
        x--;
    return x;
}
●補足2
おまけ。実数の偶数丸め。ISO C99 (ISO/IEC 9899:1999) より前の C言語処理系では標準Cライブラリ(の math.lib) に round 関数を持っていないことがあるので、そういう場合の代替手段です。
list.4 実数の偶数丸めのプログラム例 (ISO C99 より前の C言語の場合)
#include <math.h>

double
round(double x)
{
    double t;

    if (isnan(x)) return(x);
    if (isinf(x)) return(x);
    if (x >= 0.0) {
        t = floor(x);
        if (t - x <= -0.5)
            t += 1.0;
        return (t);
    } else {
        t = floor(-x);
        if (t + x <= -0.5)
            t += 1.0;
        return (-t);
    }
}

2進数の偶数丸め

整数を2進数で扱って右シフトして2の冪で割る場合にも、大抵の場合には単純に切り捨てずに偶数丸めを行った方が 誤差の蓄積が出にくくて良いです。
誤差の例として、入力が 20〜0 へと順次変化した場合の丸めの例を list.5 に示します。
list.5 2進数の2の冪での除算時の丸めの例
(シフトは 1-bit 右シフト。偶数丸めは list.6 (n = 1) による。)
入力  , シフト, 偶数丸め
i = 20, j = 10, k = 10
i = 19, j =  9, k = 10
i = 18, j =  9, k =  9
i = 17, j =  8, k =  8
i = 16, j =  8, k =  8
i = 15, j =  7, k =  8
i = 14, j =  7, k =  7
i = 13, j =  6, k =  6
i = 12, j =  6, k =  6
i = 11, j =  5, k =  6
i = 10, j =  5, k =  5
i =  9, j =  4, k =  4
i =  8, j =  4, k =  4
i =  7, j =  3, k =  4
i =  6, j =  3, k =  3
i =  5, j =  2, k =  2
i =  4, j =  2, k =  2
i =  3, j =  1, k =  2
i =  2, j =  1, k =  1
i =  1, j =  0, k =  0
i =  0, j =  0, k =  0
----------------------
計 210,    100,    105
入力を積算した場合の値 210 の半分の 105 に対して 偶数丸めの場合の積算値は 105 と誤差が少なく(この場合は零に)なっていますが、 単なる 1-bit 右シフトの積算値では 100 と 5% 少なくなっています。 これは、右シフトによる除算は単なる下位ビットの切り捨てと同じだから当然と言えます。
list.6 整数 x の n bit 右シフト時の偶数丸めのプログラム例 (n bit 右シフト, C言語の場合)

 x = (x + ((1 << (n - 1)) - 1) + ((x >> n) & 1)) >> n; 
[Dec. 1 2009] Rev.1.0
[Sep. 29 2012] Rev.1.1 : 2進数の偶数丸めの説明を追加
[Jan. 16 2016] 実数の偶数丸め関数追記
[Jan. 19 2016] 2進数の偶数丸めの補足

REFERENCE EXTERNAL LINKS
www.finetune.co.jp [Mail] © 2000 Takayuki HOSODA.
Powered by
 Finetune