KDOC 246: 浮動小数点の誤差を体感する

この文書のステータス

  • 作成
    • 2024-09-21 貴島
  • レビュー
    • 2024-10-05 貴島

概要

本を読んでいて、浮動小数点数で数値比較してはいけない、とあった。代数的に等しくても、誤差によってビット全体を比較すると等しくならないことがある。確かめてみる。

void calc(double original, double d1, char ope, double d2) {
  double result;
  switch (ope) {
  case '+':
    result = d1 + d2;
    break;
  case '-':
    result = d1 - d2;
    break;
  case '*':
    result = d1 * d2;
    break;
  case '/':
    result = d1 / d2;
    break;
  };

  printf("%s", "┏ ─────────────────────────────────────────────────────────────────── ┓\n");
  printf("original: \t%.55f\nresult: \t%.55f\n  - d1: \t%.55f\n  - ope: \t%c\n  - d2: \t%.55f\n", original, result, d1, ope, d2);
  printf("equal? => %d\n", (original == result));
  printf("%s", "┗ ─────────────────────────────────────────────────────────────────── ┛\n\n");
};

calc(0.1, 0.05, '+', 0.05);
calc(1, 0.5, '+', 0.5);
calc(0.3, 0.1, '+', 0.2);
calc(0.1, 0.09, '+', 0.01);
calc(0.01, 0.23, '-', 0.22);
calc(0.1, 1.0, '-', 0.9);
┏ ─────────────────────────────────────────────────────────────────── ┓
original: 	0.1000000000000000055511151231257827021181583404541015625
result: 	0.1000000000000000055511151231257827021181583404541015625
  - d1: 	0.0500000000000000027755575615628913510590791702270507812
  - ope: 	+
  - d2: 	0.0500000000000000027755575615628913510590791702270507812
equal? => 1
┗ ─────────────────────────────────────────────────────────────────── ┛

┏ ─────────────────────────────────────────────────────────────────── ┓
original: 	1.0000000000000000000000000000000000000000000000000000000
result: 	1.0000000000000000000000000000000000000000000000000000000
  - d1: 	0.5000000000000000000000000000000000000000000000000000000
  - ope: 	+
  - d2: 	0.5000000000000000000000000000000000000000000000000000000
equal? => 1
┗ ─────────────────────────────────────────────────────────────────── ┛

┏ ─────────────────────────────────────────────────────────────────── ┓
original: 	0.2999999999999999888977697537484345957636833190917968750
result: 	0.3000000000000000444089209850062616169452667236328125000
  - d1: 	0.1000000000000000055511151231257827021181583404541015625
  - ope: 	+
  - d2: 	0.2000000000000000111022302462515654042363166809082031250
equal? => 0
┗ ─────────────────────────────────────────────────────────────────── ┛

┏ ─────────────────────────────────────────────────────────────────── ┓
original: 	0.1000000000000000055511151231257827021181583404541015625
result: 	0.0999999999999999916733273153113259468227624893188476562
  - d1: 	0.0899999999999999966693309261245303787291049957275390625
  - ope: 	+
  - d2: 	0.0100000000000000002081668171172168513294309377670288086
equal? => 0
┗ ─────────────────────────────────────────────────────────────────── ┛

┏ ─────────────────────────────────────────────────────────────────── ┓
original: 	0.0100000000000000002081668171172168513294309377670288086
result: 	0.0100000000000000088817841970012523233890533447265625000
  - d1: 	0.2300000000000000099920072216264088638126850128173828125
  - ope: 	-
  - d2: 	0.2200000000000000011102230246251565404236316680908203125
equal? => 0
┗ ─────────────────────────────────────────────────────────────────── ┛

┏ ─────────────────────────────────────────────────────────────────── ┓
original: 	0.1000000000000000055511151231257827021181583404541015625
result: 	0.0999999999999999777955395074968691915273666381835937500
  - d1: 	1.0000000000000000000000000000000000000000000000000000000
  - ope: 	-
  - d2: 	0.9000000000000000222044604925031308084726333618164062500
equal? => 0
┗ ─────────────────────────────────────────────────────────────────── ┛

末尾付近にある数値によって桁上がりした場合に、大きな誤差となることがあるのだが、末尾の数値はなぜ出てくるのだろうか。2進数で表現できないというのはわかるのだが、小数第20位まで辿らないと見えないような値なのはなぜなのだろう。

0.5は2^(-1)なので、正確に表現できる。小数以下の数字は現れない。

関連