JPCERT コーディネーションセンター

FLP02-C. 精度の高い計算が必要な場合は浮動小数点数の使用を避ける

FLP02-C. 精度の高い計算が必要な場合は浮動小数点数の使用を避ける

コンピュータは有限の桁数しか表現できない。したがって、1/3 や 1/5 のような循環型の 2 進表現の値を最も一般的な浮動小数点表現である 2 進浮動小数点数で高い精度で表現することは不可能である。

精度の高い計算が必要な場合は、値を正確に表現できる別の表現方法を使用せよ。たとえば、10 進値の計算で、10 進数の正確な丸め処理が必要な場合、浮動小数点値ではなく値を 2 進化 10 進コードで表現する。別の選択肢は ANSI/IEEE 754-2007 で規定されている 10 進数の浮動小数点演算である。ISO/IEC WG14 は、C 言語を使って新たに 10 進数の浮動小数点演算を使用できるようにする提案を作成した [ISO/IEC DTR 24732]。

正確な計算が必要な場合、10 進数と 2 進数のどちらを使用するかにかかわらず、結果の誤差が許容範囲内に収まるよう、最大累積誤差を慎重かつ体系的に見積ることが必要である。問題点を正しく理解するためには数値解析の手法が役に立つ。[Goldberg 91] に概要が記載されている。

違反コード

以下のコード例は、同一の 10 個の数字の平均値を計算し、平均がこの数字と一致するかを調べている。10 個の数字はすべて 10.1 であるため、一致するはずである。しかし、浮動小数点演算の精度が低いため、計算された平均値はこの数字と一致しない。

include <stdio.h>

/* 配列の平均値を返す */
float mean(float array[], int size) {
  float total = 0.0;
  size_t i;
  for (i = 0; i  < size; i++) {
    total += array[i];
    printf("array[%d] = %f and total is %f\n", i, array[i], total);
  }
  if (size != 0)
    return total / size;
  else
    return 0.0;
}

enum { array_size = 10 };
float array_value = 10.1;

int main(void) {
  float array[array_size];
  float avg;
  size_t i;
  for (i = 0; i  < array_size; i++) {
    array[i] = array_value;
  }

  avg = mean( -array, array_size);
  printf("mean is %f\n", avg);
  if (avg == array[0]) {
    printf("array[0] is the mean\n");
  } else {
    printf("array[0] is not the mean\n");
  }
  return 0;
}

64 ビット Linux 上で GCC 4.1 を使用している場合、このプログラムは次のような出力を生成する。

array[0] = 10.100000 and total is 10.100000
array[1] = 10.100000 and total is 20.200001
array[2] = 10.100000 and total is 30.300001
array[3] = 10.100000 and total is 40.400002
array[4] = 10.100000 and total is 50.500000
array[5] = 10.100000 and total is 60.599998
array[6] = 10.100000 and total is 70.699997
array[7] = 10.100000 and total is 80.799995
array[8] = 10.100000 and total is 90.899994
array[9] = 10.100000 and total is 100.999992
mean is 10.099999
array[0] is not the mean
適合コード

上記の違反コードを修正するには、内部で加算するときに浮動小数点数を整数に置き換えればよい。float は結果を出力するときと、平均値を計算する除算のときだけに使用する。

#include <stdio.h>

/* 配列の平均値を返す */
float mean(int array[], int size) {
  int total = 0;
  size_t i;
  for (i = 0; i  < size; i++) {
    total += array[i];
    printf("array[%d] = %f and total is %f\n", i, array[i] / 100.0, total / 100.0);
  }
  if (size != 0)
    return ((float) total) / size;
  else
    return 0.0;
}

enum {array_size = 10};
int array_value = 1010;

int main(void) {
  int array[array_size];
  float avg;
  size_t i;
  for (i = 0; i  < array_size; i++) {
    array[i] = array_value;
  }

  avg = mean( -array, array_size);
  printf("mean is %f\n", avg / 100.0);
  if (avg == array[0]) {
    printf("array[0] is the mean\n");
  } else {
    printf("array[0] is not the mean\n");
  }
  return 0;
}

64 ビット Linux マシン上の GCC 4.1 では、このプログラムは次のような予想どおりの出力を生成する。

array[0] = 10.100000 and total is 10.100000
array[1] = 10.100000 and total is 20.200000
array[2] = 10.100000 and total is 30.300000
array[3] = 10.100000 and total is 40.400000
array[4] = 10.100000 and total is 50.500000
array[5] = 10.100000 and total is 60.600000
array[6] = 10.100000 and total is 70.700000
array[7] = 10.100000 and total is 80.800000
array[8] = 10.100000 and total is 90.900000
array[9] = 10.100000 and total is 101.000000
mean is 10.100000
array[0] is the mean
リスク評価

浮動小数点以外の表現を使用することで、より精度の高い計算結果が得られることがある。

レコメンデーション 深刻度 可能性 修正コスト 優先度 レベル
FLP02-C P2 L3
自動検出

Compass/ROSE はこのレコメンデーションの違反を検出できる。特に、等価演算子の引数が浮動小数点型かどうかを検査する。

参考情報
翻訳元

これは以下のページを翻訳したものです。

FLP02-C. Avoid using floating point numbers when precise computation is needed

Top へ

Topへ
最新情報(RSSメーリングリストTwitter