有一个序列:
x(n + 2)= 9/4 * x(n + 1)-1 / 2 * x(n)
x(1)= 1/3,x(2)= 1/12
确切的结果是x(n)= 4 ^(1-n)/ 3
我希望在计算中显示x(60)的舍入误差。
我的代码是
#include <stdio.h>
#include <math.h>
int main(void)
{
float x[60];
x[0] = 1./3;
x[1] = 1./12;
for (int i = 2; i < 60; i++) {
x[i] = 9./4*x[i-1]-1./2*x[i-2];
}
double y[60];
y[0] = 1./3;
y[1] = 1./12;
for (int i = 2; i < 60; i++) {
y[i] = 9./4*y[i-1]-1./2*y[i-2];
}
printf("single:%g, double:%g, exact:%g\n", x[59], y[59], pow(4,-59)/3);
return 0;
}
我用gcc编译它:
gcc seq.c
输出为:
single:1.00309e-36, double:1.71429, exact:1.00309e-36
如果我这样更改上面的代码:
#include <stdio.h>
#include <math.h>
int main(void)
{
float x[60];
x[0] = 1.f/3;
x[1] = 1.f/12;
for (int i = 2; i < 60; i++) {
x[i] = 9.f/4*x[i-1]-1.f/2*x[i-2];
}
double y[60];
y[0] = 1./3;
y[1] = 1./12;
for (int i = 2; i < 60; i++) {
y[i] = 9./4*y[i-1]-1./2*y[i-2];
}
printf("single:%g, double:%g, exact:%g\n", x[59], y[59], pow(4,-59)/3);
return 0;
}
在常量浮点数后添加“ f”以计算x数组。
输出似乎正常:
single:-9.2035e+08, double:1.71429, exact:1.00309e-36
我的问题是:
为什么在第一种情况下float数据类型的结果等于精确结果?
编译器做什么?
答案 0 :(得分:6)
float
并不比double
更精确,并且您的float
计算并没有为您提供pow(4,-59)/3
的确切结果。
这是怎么回事,您的重复周期被设计为取一个很小的舍入误差,并在每次迭代时都将其放大。在精确的数学运算中,每个值应恰好是前一个值的四分之一,但是如果由于舍入误差而导致的值不完全是四分之一,那么每一步的差异都会放大。
由于可表示值的四分之一始终是可表示的(直到遇到次正规数和下溢问题),因此重复具有附加属性:如果计算的精度足够超过结果的精度存储,然后将结果四舍五入到较低的精度以进行存储,将精确到四分之一的值。 (9./4
和1./2
因素的选择使该属性的重现性更加强大,即使在舍入存储之前,结果也恰好是旧值的四分之一。)
使用双精度数时,如果使用的是编译器和编译器设置,则会出现舍入错误并被放大。使用浮点运算时,计算将以双精度执行,从而消除了由于上述属性而导致的重复步骤中的舍入误差,因此无需进行任何放大。如果以长双精度执行双精度计算,则会发生同样的事情。
通过使用%a
格式说明符以十六进制表示法打印浮点数,让我们仔细看看产生的确切值。看起来像0x1.5555555555558p-6
,其中0x
和p
之间的部分是一个十六进制数,而p
之后的部分是一个十进制数,代表2的乘幂十六进制数。在这里,0x1.5555555555558p-6
代表0x1.5555555555558乘以2 ^ -6。 %a
格式始终会打印浮点数或双精度数的确切值,而%g
会四舍五入。
我们还将展示第三次计算,将结果存储为双精度,但以长双精度进行数学运算。
我们修改后的程序如下:
#include <stdio.h>
#include <math.h>
int main(void)
{
float x[60];
x[0] = 1./3;
x[1] = 1./12;
for (int i = 2; i < 60; i++) {
x[i] = 9./4*x[i-1]-1./2*x[i-2];
}
double y[60];
y[0] = 1./3;
y[1] = 1./12;
for (int i = 2; i < 60; i++) {
y[i] = 9./4*y[i-1]-1./2*y[i-2];
}
double z[60];
z[0] = 1./3;
z[1] = 1./12;
for (int i = 2; i < 60; i++) {
z[i] = (long double) 9./4*z[i-1] - (long double) 1./2*z[i-2];
}
printf("float:%a, double:%a, double2:%a, formula:%a\n", x[59], y[59], z[59], pow(4,-59)/3);
for (int i = 0; i < 60; i++) {
printf("%d %a %a %a\n", i, x[i], y[i], z[i]);
}
return 0;
}
这是输出。我本来打算简化这一点,但事实证明,要做到这一点,而又不掩盖模式中有趣的部分,就很难做到这一点:
float:0x1.555556p-120, double:0x1.b6db6db6db6dap+0, double2:0x1.5555555555555p-120, formula:0x1.5555555555555p-120
0 0x1.555556p-2 0x1.5555555555555p-2 0x1.5555555555555p-2
1 0x1.555556p-4 0x1.5555555555555p-4 0x1.5555555555555p-4
2 0x1.555556p-6 0x1.5555555555558p-6 0x1.5555555555555p-6
3 0x1.555556p-8 0x1.555555555557p-8 0x1.5555555555555p-8
4 0x1.555556p-10 0x1.555555555563p-10 0x1.5555555555555p-10
5 0x1.555556p-12 0x1.5555555555c3p-12 0x1.5555555555555p-12
6 0x1.555556p-14 0x1.5555555558c3p-14 0x1.5555555555555p-14
7 0x1.555556p-16 0x1.5555555570c3p-16 0x1.5555555555555p-16
8 0x1.555556p-18 0x1.5555555630c3p-18 0x1.5555555555555p-18
9 0x1.555556p-20 0x1.5555555c30c3p-20 0x1.5555555555555p-20
10 0x1.555556p-22 0x1.5555558c30c3p-22 0x1.5555555555555p-22
11 0x1.555556p-24 0x1.5555570c30c3p-24 0x1.5555555555555p-24
12 0x1.555556p-26 0x1.5555630c30c3p-26 0x1.5555555555555p-26
13 0x1.555556p-28 0x1.5555c30c30c3p-28 0x1.5555555555555p-28
14 0x1.555556p-30 0x1.5558c30c30c3p-30 0x1.5555555555555p-30
15 0x1.555556p-32 0x1.5570c30c30c3p-32 0x1.5555555555555p-32
16 0x1.555556p-34 0x1.5630c30c30c3p-34 0x1.5555555555555p-34
17 0x1.555556p-36 0x1.5c30c30c30c3p-36 0x1.5555555555555p-36
18 0x1.555556p-38 0x1.8c30c30c30c3p-38 0x1.5555555555555p-38
19 0x1.555556p-40 0x1.8618618618618p-39 0x1.5555555555555p-40
20 0x1.555556p-42 0x1.e186186186186p-39 0x1.5555555555555p-42
21 0x1.555556p-44 0x1.bc30c30c30c3p-38 0x1.5555555555555p-44
22 0x1.555556p-46 0x1.b786186186185p-37 0x1.5555555555555p-46
23 0x1.555556p-48 0x1.b6f0c30c30c3p-36 0x1.5555555555555p-48
24 0x1.555556p-50 0x1.b6de186186185p-35 0x1.5555555555555p-50
25 0x1.555556p-52 0x1.b6dbc30c30c3p-34 0x1.5555555555555p-52
26 0x1.555556p-54 0x1.b6db786186185p-33 0x1.5555555555555p-54
27 0x1.555556p-56 0x1.b6db6f0c30c3p-32 0x1.5555555555555p-56
28 0x1.555556p-58 0x1.b6db6de186185p-31 0x1.5555555555555p-58
29 0x1.555556p-60 0x1.b6db6dbc30c3p-30 0x1.5555555555555p-60
30 0x1.555556p-62 0x1.b6db6db786185p-29 0x1.5555555555555p-62
31 0x1.555556p-64 0x1.b6db6db6f0c3p-28 0x1.5555555555555p-64
32 0x1.555556p-66 0x1.b6db6db6de185p-27 0x1.5555555555555p-66
33 0x1.555556p-68 0x1.b6db6db6dbc3p-26 0x1.5555555555555p-68
34 0x1.555556p-70 0x1.b6db6db6db785p-25 0x1.5555555555555p-70
35 0x1.555556p-72 0x1.b6db6db6db6fp-24 0x1.5555555555555p-72
36 0x1.555556p-74 0x1.b6db6db6db6ddp-23 0x1.5555555555555p-74
37 0x1.555556p-76 0x1.b6db6db6db6dbp-22 0x1.5555555555555p-76
38 0x1.555556p-78 0x1.b6db6db6db6dap-21 0x1.5555555555555p-78
39 0x1.555556p-80 0x1.b6db6db6db6dap-20 0x1.5555555555555p-80
40 0x1.555556p-82 0x1.b6db6db6db6dap-19 0x1.5555555555555p-82
41 0x1.555556p-84 0x1.b6db6db6db6dap-18 0x1.5555555555555p-84
42 0x1.555556p-86 0x1.b6db6db6db6dap-17 0x1.5555555555555p-86
43 0x1.555556p-88 0x1.b6db6db6db6dap-16 0x1.5555555555555p-88
44 0x1.555556p-90 0x1.b6db6db6db6dap-15 0x1.5555555555555p-90
45 0x1.555556p-92 0x1.b6db6db6db6dap-14 0x1.5555555555555p-92
46 0x1.555556p-94 0x1.b6db6db6db6dap-13 0x1.5555555555555p-94
47 0x1.555556p-96 0x1.b6db6db6db6dap-12 0x1.5555555555555p-96
48 0x1.555556p-98 0x1.b6db6db6db6dap-11 0x1.5555555555555p-98
49 0x1.555556p-100 0x1.b6db6db6db6dap-10 0x1.5555555555555p-100
50 0x1.555556p-102 0x1.b6db6db6db6dap-9 0x1.5555555555555p-102
51 0x1.555556p-104 0x1.b6db6db6db6dap-8 0x1.5555555555555p-104
52 0x1.555556p-106 0x1.b6db6db6db6dap-7 0x1.5555555555555p-106
53 0x1.555556p-108 0x1.b6db6db6db6dap-6 0x1.5555555555555p-108
54 0x1.555556p-110 0x1.b6db6db6db6dap-5 0x1.5555555555555p-110
55 0x1.555556p-112 0x1.b6db6db6db6dap-4 0x1.5555555555555p-112
56 0x1.555556p-114 0x1.b6db6db6db6dap-3 0x1.5555555555555p-114
57 0x1.555556p-116 0x1.b6db6db6db6dap-2 0x1.5555555555555p-116
58 0x1.555556p-118 0x1.b6db6db6db6dap-1 0x1.5555555555555p-118
59 0x1.555556p-120 0x1.b6db6db6db6dap+0 0x1.5555555555555p-120
在这里,我们首先看到float
计算并没有产生pow
公式给出的确切值(它的精度不够高),但是它足够接近以至于%g
的舍入隐藏了差异。我们还看到,float
的值每次精确地减小4倍,而变化的double
计算值也是如此。原始double
版本中的double
值几乎是从 开始的,一旦放大的误差使计算不堪重负,然后就发散了。最终,这些值开始增加2倍,而不是减少4倍。
答案 1 :(得分:0)
这是一个计算,不能使用浮点数完成。您要添加大小数字,而舍入误差对于这种计算而言太大。
1/3和1/12只是幸运的浮点计算初始起点。对于其他初始值,这两种计算都得出几乎相同的结果,而且通常都错了。
答案 2 :(得分:0)
在我看来,您很清楚浮点舍入错误会导致完全错误的结果。实际上,似乎您对示例1中的“正确”结果感到惊讶,而不是对示例2中的错误结果感到惊讶。
嗯,舍入错误可能会导致极其错误的结果,但是您不能认为舍入错误将总是 导致极其错误的结果。有时舍入误差只会导致较小的误差,而在其他时候,舍入误差会导致整个计算不稳定并导致极端误差。
@ user2357112 https://stackoverflow.com/a/55194247/4386427的回答很好地描述了您的具体情况。
但是您的问题的一部分仍然没有得到答案:
编译器做什么?
我假设您是在问为什么使用此代码
a) x[i] = 9./4*x[i-1]-1./2*x[i-2];
给出的结果与此代码不同
b) x[i] = 9.f/4*x[i-1]-1.f/2*x[i-2];
^ ^
答案是,情况a)要求计算以双精度进行,因为9.
的类型为double,而情况b)允许以单精度进行计算,因为所有类型均为浮点型。
如果编译器决定使用单精度运算而不是双精度运算,则情况a)和情况b)的舍入误差将有所不同。如上所述,不同的舍入误差可能会导致不同的结果。
没有唯一的答案可以解释编译器将执行的操作,因为不同的编译器可能会给出不同的结果。下面是一个使用https://godbolt.org/和x86-64 gcc 8.3和-O0生成的示例。
为简单起见,它仅涵盖9./4*x[i-1]
与9.f/4*x[i-1]
,而我仅复制了不同的行。
Case 9./4*x[i-1]:
movss xmm0, DWORD PTR [rax]
cvtss2sd xmm1, xmm0
movsd xmm0, QWORD PTR .LC0[rip]
mulsd xmm0, xmm1
和
Case 9.f/4*x[i-1]:
movss xmm1, DWORD PTR [rax]
movss xmm0, DWORD PTR .LC0[rip]
mulss xmm0, xmm1
cvtss2sd xmm0, xmm0
可以看出,区别在于使用单精度(mulss
)和双精度(mulsd
)。
因此得出结论:
编译器做什么?
它使用不同的浮点精度进行计算,导致不同的舍入误差,这又导致不同的结果。