多少の誤差を無視させ同じ値と判定させるには!?
処理を作成した時に、どんな入力値に対しても
正しく動作しているかを調べるのに苦労する。
入力値を変えながら100回ぐらい結果を出してみる。
その数値を眺めても結果が正しいかなんてわからん!
処理が単純ならエクセルなんかで検算するのも手だ。
図にするのも良いだろう。
だが演算が複雑で入力する値が多い場合や
数百万回の動作確認を行いたい場合には、
別なアプローチで作成した処理の結果と比較するのが
有効な手段だろう。
同じ値を入力すれば、同じ結果になる。
違った場合には、どっちかの処理がバグっている訳だ。
二つの処理を作らなくてはならないが
バグの発見がグッと楽になる。
100万回ぐらいの試行錯誤もPC任せで出来る。
出力結果が異なった部分だけ抜き出して検証すれば良い訳だ。
しかし、処理中の数値の変換過程やらなんやらで
出力結果が微妙に違ったりする!
まあ、誤差と言う奴でしょうか…。
整数(『int』)なら大抵これで終わりなはずだ。
if(a==b){/* 同じ値だ!*/}
だが、浮動小数点(『float/double』で同じ物かを判断する場合、
大抵こんな風に小数点以下を切って比較するだろう。
if((int)a==(int)b){/*同じ値だ!*/}
if((int)(a*100)==(int)(b*100)){/*同じ値だ!*/}
上記は、『a/b』の有効桁が固定している場合には良いのだが
出力結果が30~-30桁なんて幅のある数値だと
上記のような処理では、対応できない…。
で、どうしたら良い物か考えた…
…
ハッ!と閃いた!
元の値と差分の値の桁数を比較すればOKじゃねぇ?
基本の考え方は、以下の通り。
1.同じものなら差分は、0
2.ほぼ同じものなら桁数は同じ
3.ほぼ同じものなら差分は元の桁数より桁違いに小さい
『3』の『桁違いに小さい』がポイントね!
ちなみに-7桁とかの小さ過ぎる数値は、0とみなす。
0に限り無く近いと誤差が大きくなり過ぎて比較できないので。
で、桁数で同じかどうか判定する処理を作ってみた。
#include <windows.h>
#include <stdio.h>
#include <math.h>
#define BORDER_ZERO -7 // ゼロとみなす桁数
#define BORDER_DIFFER -4 // 誤差として切り捨てる桁数
BOOL PlaceComp( double a, double b )
{
double c, la, lb, lc;
c = a - b;
if( c == 0 )
return TRUE; // 同じ物です。
la = a == 0 ? BORDER_ZERO : log10( fabs( a ) );
lb = b == 0 ? BORDER_ZERO : log10( fabs( b ) );
lc = c == 0 ? BORDER_ZERO : log10( fabs( c ) );
if( ( la <= BORDER_ZERO ) && ( lb <= BORDER_ZERO ) )
return TRUE; // 小さすぎる値は、ゼロとみなします。
if( ( int )la != ( int )lb )
return FALSE; // 桁数が異なる場合、明らかに違う値です。
if( ( int )lc <= ( ( int )la + BORDER_DIFFER ) )
return TRUE; // 差分の桁数が十分小さいなら同じものとします。
return FALSE;
}
void main( void )
{
double table[][2] =
{
{ -2.2494911121205649e-031, 7.4983033152370995e-032, },
{ 3.8268344402313232, 3.8268344402313232, },
{ 1.0800063947812803e-015, 1.0800063947812803e-015, },
{ -9.4531962702149031e-008, 0.00000000000000000, },
{ 6.2361249923706055, 6.2361254692077637, },
{ 2.7059803009033203e21, 2.7059805393218994e21, },
{ 3.1259803009033203e21, 3.1269805393218994e21, },
};
int i, f;
for( i = 0 ; i < sizeof( table ) / sizeof( *table ) ; i++ )
{
f = PlaceComp( table[i][0], table[i][1] );
printf( f ? "same\n" : "differ\n" );
}
}
結果
same
same
same
same
differ
same
differ
『BORDER_DIFFER』を『-6』にすると『differ』が一つ減る。
『-3』にすると全て『same』になる。
誤差がどの程度かで『define』の設定値を合わせて変えればOKなはず。
実際、必要に迫られたので作ってみたけど結構使えるかも…。
取り敢えずメモとして書いておこう…。
| 固定リンク
コメント