For someone who always likes to remind people about floating point errors, I was caught asleep at the wheel in this case.
If you run the following program,
/* 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;
}
you get this output:
C:\...\Temp> cl /fp:precise /O2 rrr.c
C:\...\Temp> rrr.exe
2.25
But, that is due to floating point error as @JJoao points out. He outlines a specific way you can deal with this particular issue.
Another way is to utilize an arbitrary precision math library to help you. First, let's verify the issue using a quick Perl script:
#!/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);
Output:
C:\...\Temp> perl t.pl
2.25
Same as the C program.
Now, use Perl's bignum:
C:\...\Temp> perl -Mbignum t.pl
5.83333
This increased accuracy comes at a performance cost. Without bignum
, the script runs in 0.125 seconds with about 0.094 is spent doing other things than the calculation. With bignum
, it takes about two seconds.
Now, modern C provides various facilities for manipulating how floating numbers are rounded. In this particular case, given the nature of the floor function, setting the rounding mode to FE_DOWNWARD
will fix the issue:
#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;
}
Output:
C:\...\Temp> cl /O2 /fp:precise rrr.c
/out:rrr.exe
C:\...\Temp> rrr
5.83333