为什么涉及地板功能的这个公式没有给出我期望的结果?

时间:2015-05-07 09:58:00

标签: c

这是我需要编程的数学计算 - 将序列a n 定义为

  • a 1 = 1
  • a (n + 1) = 1 /(2×[a n ] - a n + 1)其中[x]是floor函数

如果 2014 = p / q,请找到p + q

而且,这是我尝试过的代码:

#include <stdio.h>
#include <math.h>

main() {
   int n=1;
   double an=1.0;
   double an_floor=0.0;

   while (n<=2014) {
      an_floor = floor(an);
      an = 1 / (2*an_floor-an+1);
      n = n + 1;
   }

   printf("%lf", an);

   return 0;
}

问题;

  • 我无法编译(使用网络编译器,如键盘等)

  • 不知道如何制作结果

  • 分数结果错误

2 个答案:

答案 0 :(得分:1)

对于那些总是喜欢提醒人们浮点错误的人来说,在这种情况下我被抓住了。

如果您运行以下程序,

/* 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提供了各种设施来操纵浮动数字的舍入方式。在这种特殊情况下,考虑到楼层功能的性质,将舍入模式设置为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

答案 1 :(得分:1)

这是一个危险的序列:

floor(潜在表示错误的值)=大错误!!

floor(3-epsilon)= 2
floor(3+epsilon)= 3

大量累计实数计算=大错误!!

您需要研究数字精度错误!

使用我们得到的OP代码

1
0.5
2
0.333333
1.5
0.666667
3
0.5           <===== Big precision ERROR it should be 0.25
2
1
2.2518e+15A   <===== infinity
4.44089e-16   <===== zero
1
0.5
....(wrong) cyclic behavior

final(错!!)结果= 2.2518e+15A <===== infinity

更新:但我们该怎么办?

1)floor问题:用floor(x)替换floor(x + 2*expectedError)

2)关于累积错误:将double表示替换为(v1:int, v2:int) 分数表示。An -> v1/v2

newAn = newv1/newv2 =
      = 1 / (2 * flo(An) + 1 - An) =
      = 1 / (2 * flo(v1/v2) + 1 - v1/v2) =
      = ... =
      = v2 / ( 2 * v2 * flo(v1/v2) + v2 - v1) 

这样

newv1 = v2
newv2 =  2 * v2 * flo(v1/v2) + v2 - v1

在C:

main() {
   int v1=1, v2=1, n=1, oldv1;
   double an_floor;

   while (n<=2014) {
     an_floor = floor((0.0+v1)/v2 + 0.0000000001);

     oldv1=v1;
     v1=v2;
     v2=an_floor * v2 * 2 + v2 - oldv1;
     n++;
     //  printf("%g\n", (0.0+v1)/v2);
  }

  printf("%d/%d=%g\n", v1, v2, (0.0+v1)/v2);   //      35/6=5.83333
}

\ thanks {SinanÜnür}