我正在尝试使用 epsilon 比较具有双精度的值。但是,我有一个问题 - 最初我认为差异应该等于 epsilon,但事实并非如此。此外,当我尝试使用连续乘法检查二进制表示时,发生了一些奇怪的事情,我感到很困惑,因此我很感激你对问题的解释和对我的思维方式的评论
#include <stdio.h>
#define EPSILON 1e-10
void double_equal(double a, double b) {
printf("a: %.12f, b: %.12f, a - b = %.12f\n", a, b, a - b);
printf("a: %.12f, b: %.12f, b - a = %.12f\n", a, b, b - a);
if (a - b < EPSILON) printf("a - b < EPSILON\n");
if (a - b == EPSILON) printf("a - b == EPSILON\n");
if (a - b <= EPSILON) printf("a - b <= EPSILON\n");
if (b - a <= EPSILON) printf("b - a <= EPSILON\n");
}
int main(void) {
double wit1 = 1.0000000001;
double wit2 = 1.0;
double_equal(wit1, wit2);
return 0;
}
输出是:
a: 1.000000000100, b: 1.000000000000, a - b = 0.000000000100
a: 1.000000000100, b: 1.000000000000, b - a = -0.000000000100
b - a <= EPSILON
如果我们不在数字 ( #define EPSILON 1e-10F
) 之后提供“F”/“f”符号,则 C 中的数字常量被声明为双精度数,因此我在这里看不到这个问题中的转换问题。因此,我为这些特定示例创建了非常简单的程序(我知道它应该包括处理将整数部分转换为二进制数)。
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
char* convert(double a) {
char* res = malloc(200);
int count = 0;
double integral;
a = modf(a, &integral);
if (integral == 1) {
res[count++] = integral + '0';
res[count++] = '.';
} else {
res[count++] = '0';
res[count++] = '.';
}
while(a != 0 && count < 200) {
printf("%.100f\n", a);
a *= 2;
a = modf(a, &integral);
if (integral == 1) res[count++] = integral + '0';
else res[count++] = '0';
}
res[count] = '\0';
return res;
}
int main(void) {
double wit1 = 1.0000000001;
double diff = 0.0000000001;
char* res = convert(wit1);
char* di = convert(diff);
printf("this: %s\n", res);
printf("diff: %s\n", di);
return 0;
}
直接输出:
this: 1.0000000000000000000000000000000001101101111100111
diff: 0.00000000000000000000000000000000011011011111001101111111011001110101111011110110111011
第一个问题:为什么差异中有这么多结尾的零?为什么二进制点后的结果不同?
但是,如果我们查看计算过程和小数部分,打印出来(我只展示前几行):
1.0000000001:
0.0000000001000000082740370999090373516082763671875000000000000000000000000000000000000000000000000000
0.0000000002000000165480741998180747032165527343750000000000000000000000000000000000000000000000000000
0.0000000004000000330961483996361494064331054687500000000000000000000000000000000000000000000000000000
0.0000000001:
0.0000000001000000000000000036432197315497741579165547065599639608990401029586791992187500000000000000
0.0000000002000000000000000072864394630995483158331094131199279217980802059173583984375000000000000000
0.0000000004000000000000000145728789261990966316662188262398558435961604118347167968750000000000000000
第二个问题:为什么会有这么多奇怪的结尾数字?这是由于无法精确表示十进制值的浮点运算的结果吗?
分析减法,我可以看到为什么结果大于 epsilon。我遵循以下程序:
- 为要减去的序列准备一个零一的补序列
- “添加”序列
- 减去开头的那个,添加到最右边的位
所以:
1.0000000000000000000000000000000001101101111100111
- 1.0000000000000000000000000000000000000000000000000
|
\/
1.0000000000000000000000000000000001101101111100111
"+"0.1111111111111111111111111111111111111111111111111
--------------------------------------------------------
10.0000000000000000000000000000000001101101111100110
|
\/
0.0000000000000000000000000000000001101101111100111
与 的计算值比较epsilon
:
0.000000000000000000000000000000000110110111110011 0 1111111011001110101111011110110111011
0.000000000000000000000000000000000110110111110011 1
空格表示差异。
第三个问题:如果我不能比较等于epsilon的值,我是否需要担心?我认为这种情况表明了与 epsilon 的公差间隔是为了什么而制定的。但是,有什么我应该改变的吗?