对于总是喜欢提醒人们注意浮点错误的人,在这种情况下,我在方向盘上睡着了。
如果你运行以下程序,
/* a1 = 1, a(n+1) = 1/(2*[an]-an+1) ([x] is floor function)
* a2014 = p/q
* find p+q
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define LAST_ELEMENT 2014
static double
next_element(double prev) {
return 1.0 / (2.0 * floor(prev) - prev + 1.0);
}
int main(void) {
int i = 0;
double last = 1.0;
for (i = 0; i < LAST_ELEMENT; i += 1) {
last = next_element( last );
}
printf("%g\n", last);
return EXIT_SUCCESS;
}
你得到这个输出:
C:\...\Temp> cl /fp:precise /O2 rrr.c
C:\...\Temp> rrr.exe
2.25
但是,这是由于 @JJoao points out 的浮点错误。他概述了处理此特定问题的具体方法。
另一种方法是利用任意精度数学库来帮助您。首先,让我们使用快速 Perl 脚本来验证问题:
#!/usr/bin/env perl
use strict;
use warnings;
use POSIX qw( floor );
sub next_element { '1' / (('2' * floor($_[0])) - $_[0] + '1.0') }
sub main {
my ($x, $n) = @_;
$x = next_element( $x ) for 1 .. $n;
printf("%g\n", $x);
}
main(1, 2014);
输出:
C:\...\Temp> perl t.pl
2.25
与 C 程序相同。
现在,使用 Perl 的bignum:
C:\...\Temp> perl -Mbignum t.pl
5.83333
这种提高的准确性是以性能为代价的。如果没有bignum,脚本将在 0.125 秒内运行,其中大约 0.094 用于计算以外的其他事情。使用bignum,大约需要两秒钟。
现在,现代 C 语言提供了各种工具来操作浮点数的四舍五入方式。在这种特殊情况下,鉴于 floor 函数的性质,将舍入模式设置为 FE_DOWNWARD 将解决问题:
#include <fenv.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define LAST_ELEMENT 2014
static double
next_element(double prev) {
return 1.0 / (2.0 * floor(prev) - prev + 1.0);
}
int main(void) {
int i = 0;
double last = 1.0;
const int original_rounding = fegetround();
fesetround(FE_DOWNWARD);
for (i = 0; i < LAST_ELEMENT; i += 1) {
last = next_element(last);
}
fesetround(original_rounding);
printf("%g\n", last);
return EXIT_SUCCESS;
}
输出:
C:\...\Temp> cl /O2 /fp:precise rrr.c
/out:rrr.exe
C:\...\Temp> rrr
5.83333