对于那些总是喜欢提醒人们浮点错误的人,在这种情况下,我被抓住了。
如果您运行下面的程序,
/* 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
但是,这是由于浮点误差@JJoaopoints 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提供了各种设施来操纵浮动数字的四舍五入。在这种特殊情况下,考虑到地板函数的性质,舍入模式设置为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
递归可以在这里是一个更好的选择。 –
当我们仔细分析它时,这实际上是一个**优秀的问题**! – JJoao