質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

3回答

3533閲覧

double型同士の比較演算・内部表現

dotbot

総合スコア10

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2020/06/06 10:07

前提・実現したいこと

double型の内部表現を明らかにし、誤差に対する理解を深める目的でdouble型をビット単位で表示する関数を
ウェブサイトを参考にしながら作りました。(https://www.k-cube.co.jp/wakaba/server/floating_point.html)
double型の10.1, 10.2, 20.3をそれぞれビット表示し、誤差が原因で10.1 + 10.2 != 20.3であることを確認しました。
そして、計算機イプシロンを左辺に加えることで両辺のビット表示が同じになることを確認し、それらを比較演算したところ、予想に反し、両辺の値が違うという結果が出ました。
どのようなことが起こっているのか教えていただけるとありがたいです。

該当のソースコード

C

1#include <stdio.h> 2#include <float.h> 3 4void dump(unsigned char *p); 5 6void main() { 7 8 double x, y, z; 9 x = 10.1; 10 y = 10.2; 11 z = 20.3; 12 union { 13 double d; 14 unsigned char c[8]; 15 } uni; 16 17 uni.d = x + y; 18 printf(" x + y: "); 19 dump(uni.c); 20 21 uni.d = DBL_EPSILON + x + y; 22 printf("eps + x + y: "); 23 dump(uni.c); 24 25 uni.d = z; 26 printf(" z: "); 27 dump(uni.c); 28 29 if (DBL_EPSILON + x + y != z) 30 puts("eps + x + y != z"); 31} 32void dump(unsigned char *p) { 33 int i, j; 34 35 //p[7]から下ってアクセスしていく 36 for (i = 7; i >= 0; i--) { 37 38 //1バイトを2進数表示する 39 for (j = 7; j >= 0; j--) { 40 printf("%d", (p[i] >> j) % 2); 41 } 42 putchar(' '); 43 } 44 putchar('\n'); 45}

実行結果

x + y: 01000000 00110100 01001100 11001100 11001100 11001100 11001100 11001100 eps + x + y: 01000000 00110100 01001100 11001100 11001100 11001100 11001100 11001101 z: 01000000 00110100 01001100 11001100 11001100 11001100 11001100 11001101 eps + x + y != z

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

Windows10でやってみましたが、コンパイラによって違いますね。

plain

1gcc version 10.1.0 (Rev3, Built by MSYS2 project) 2 x + y: 01000000 00110100 01001100 11001100 11001100 11001100 11001100 1100 1100 3eps + x + y: 01000000 00110100 01001100 11001100 11001100 11001100 11001100 1100 1100 4 z: 01000000 00110100 01001100 11001100 11001100 11001100 11001100 1100 1101 5eps + x + y != z

で、見たとおりです。
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borlandだと、質問文と同じ結果になります。

x86系の浮動小数点レジスタは80bitで仮数部64bitなので、doubleへの格納で丸めが発生しますが、その丸めの仕方がコンパイラによって違うのでしょう。
Cでの!=は、浮動小数点同士を比べているということでしょう。

投稿2020/06/06 10:46

otn

総合スコア85778

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

otn

2020/06/06 10:51

DBL_EPSILON + x + y を一旦変数に入れて、その変数と z を比べれば 等しくなると思います。
dotbot

2020/06/06 14:03

なるほど...計算した時点では仮数部が64bitもあるんですね...勉強になります。
guest

0

じつは、CPU内部の浮動小数点演算ユニットの演算幅は64ビットではなかったりします
doubleとして表現する場合は、64ビットに丸められますが、内部演算では、64ビットとして行われるとは限りません。

ということで、この演算が真となるか偽となるかは、CPUによって変わったりなんかします。

結論、浮動小数点数の==あるいは!=の比較はしてはいけません

インテル(R) アーキテクチャ (IA) 浮動小数点ユニット (FPU)、ストリーミング SIMD 拡張命令 (SSE)、ストリーミング SIMD 拡張命令2 (SSE2) を使用した浮動小数点算術演算 - w_fp_precision_j.pdf

投稿2020/06/06 10:21

y_waiwai

総合スコア88024

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2020/06/06 10:26

double の精度が CPU によって変わるんですか?
y_waiwai

2020/06/06 10:28

doubleじゃなく、FPUの搭載有無で内部演算結果が変わります
Zuishin

2020/06/06 10:54

私の PC では eps + x + y の表示が質問とは異なるものになりました。なるほどありそうです。
dotbot

2020/06/06 14:04

そうなんですね...浮動小数点の比較の際は気を付けます。
guest

0

ちょっと面白そうだったので詳しく調べてみました。
otnさんの回答を参考にBorland C++ 5.5.1 for Win32で(unionの宣言でエラーが出たので位置だけ変えて)コンパイルしたところ、以下のようになりました。(抜粋)

x86

1 2 0040115C: C7 45 F8 33 33 33 mov dword ptr [ebp-8],33333333h 3 33 4 00401163: C7 45 FC 33 33 24 mov dword ptr [ebp-4],40243333h 5 40 6 0040116A: C7 45 F0 66 66 66 mov dword ptr [ebp-10h],66666666h 7 66 8 00401171: C7 45 F4 66 66 24 mov dword ptr [ebp-0Ch],40246666h 9 40 10 00401178: C7 45 E8 CD CC CC mov dword ptr [ebp-18h],0CCCCCCCDh 11 CC 12 0040117F: C7 45 EC CC 4C 34 mov dword ptr [ebp-14h],40344CCCh 13 40 1415 004011A0: D9 05 04 12 40 00 fld dword ptr ds:[00401204h] 16 004011A6: DC 45 F8 fadd qword ptr [ebp-8] 17 004011A9: DC 45 F0 fadd qword ptr [ebp-10h] 18 004011AC: DD 1B fstp qword ptr [ebx] 1920 004011DD: D9 05 04 12 40 00 fld dword ptr ds:[00401204h] 21 004011E3: DC 45 F8 fadd qword ptr [ebp-8] 22 004011E6: DC 45 F0 fadd qword ptr [ebp-10h] 23 004011E9: DC 5D E8 fcomp qword ptr [ebp-18h] 2425 00401203: 00 00 add byte ptr [eax],al 26 00401205: 00 80 25 55 8B EC add byte ptr [eax+EC8B5525h],al

動作を説明すると、
10.1(をdouble精度で表したもの; 以下同)と10.2と10.3をメモリに用意、
x87命令で
DBL_EPSILONと10.1と10.2をそれぞれ足し、doubleとしてメモリに書く
DBL_EPSILONと10.1と10.2をそれぞれ足し、20.3と比較
という動作をしています。
(ところでDBL_EPSILONはfloatで持ってるんですね。ちょっと意外)

一方そのへんのgccでコンパイルしたところ、x87命令は使われずSSE命令が使われていました。これはdouble精度の演算です。
つまりotnさんの言うように「丸めの仕方がコンパイラによって違う」のではなく、演算の精度がそもそも異なる可能性が高いです。丸めの仕方(切り上げか切り下げかなど)がコンパイラによって違うというのは考えづらいと思います。

x87精度の演算でDBL_EPSILON+10.1+10.2は20.3とは異なりますが、その値をdoubleへ丸めると20.3に丸まります。
一方double精度でDBL_EPSILON+10.1=10.1であり、10.1+10.2は丸めた際に20.3とは異なる値です。

投稿2020/06/06 17:25

ikadzuchi

総合スコア3047

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問