Jacobi方法使用double,使用float失败。有什么问题?

时间:2016-11-08 22:21:56

标签: c double precision

我编写了一个小程序,用Jacobi(迭代)方法求解n个方程组。以下是代码:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main() {

float *a, *b, *x, *xnew, temp;
int i, j, k, maxiter=10000000, n=4;

a = malloc(n*n*sizeof(*a));
b = malloc(n*sizeof(*b));
x = malloc(n*sizeof(*x));
xnew = malloc(n*sizeof(*xnew));

srand((unsigned) time(NULL));

//  Filling the matrix
for (i=0;i<=n-1;i++) {
    for (j=0;j<=n-1;j++) {
        a[n*i+j] = rand()%60;
    }
    b[i] = rand();
    x[i] = rand();
    xorg[i]=x[i];
}

//  Establishing diagonal dominance
for (i=0;i<=n-1;i++) {
    temp=0;
    for (j=0;j<=n-1;j++) {
        if (j==i) {continue;}
        temp = temp + a[n*i+j];
    }
    a[n*i+i] = temp+1;
}

//  Solve the system. Break when residue is low
for (k=0;k<=maxiter-1;k++) {
    for (i=0;i<=n-1;i++) {
        temp=0;
        for (j=0;j<=n-1;j++) {
            if (j==i) {continue;}
            temp = temp + a[n*i+j]*x[j];
            }
        xnew[i] = (b[i]-temp)/a[n*i+i];
    }
    temp=0;
    for (i=0;i<=n-1;i++) {
        temp = temp + fabs(x[i]-xnew[i]);
        x[i]=xnew[i];
    }
    if (temp<0.0001) {
        break;
    }
}

printf("Iterations = %d\n",k-1);

return 0;
}

突破循环标准非常容易。这个程序永远不会失败。然而它显然没有收敛(它耗尽了循环中的所有迭代),除非我将浮点数更改为双精度数。浮子比这更精确。怎么了? 在Windows 7下使用CodeBlocks 16.01进行编译,即使这很重要。

2 个答案:

答案 0 :(得分:0)

if (temp<0.0001) {对于给定float和值的请求来说太精细了。

通过添加x[i]xnew[i]之差的ULP来尝试不同的限制方法。

#include <assert.h>
#include <stdint.h>

static uint32_t ULPf(float x) {
  union {
    float f;
    uint32_t u32;
  } u;
  assert(sizeof(float) == sizeof(uint32_t));
  u.f = x;
  if (u.u32 & 0x80000000) {
    u.u32 ^=  0x80000000;
    return    0x80000000 - u.u32;
  }
  return u.u32 + 0x80000000;
}

static uint32_t ULP_diff(float x, float y) {
  uint32_t ullx = ULPf(x);
  uint32_t ully = ULPf(y);
  if (x > y) return ullx - ully;
  return ully - ullx;
}

...

  uint64_t sum0 = -1;
  unsigned increase = 0;
  for (k = 0; k <= maxiter - 1; k++) {
    ...
    uint64_t sum = 0;
    for (i = 0; i <= n - 1; i++) {
      uint32_t e = ULP_diff(x[i], xnew[i]);
      // printf("%u %e %e %llu\n", i, x[i],  xnew[i], (unsigned long long) e);
      sum += e;
      x[i] = xnew[i];
    }
    if (sum < sum0) {
      // answer is converging
      sum0 = sum;
      increase = 0;
    } else {
      increase++;
      // If failed to find a better answer in `n` iterations and 
      //   code did at least n*N iterations, break.
      if (increase > n && k > n*n) break;
    }

答案 1 :(得分:0)

似乎float数据类型没有上述算法所需的精度,给定方式已编码。该算法确实收敛,但是&#34;残留&#34;永远不会低到退出循环。

我理解这一点的方式是,由于float变量的内部存储方式,您无法使用极小(0.0001)和极大(RAND_MAX)数字进行计算,期望合理的准确性,如上例所示(temp在最里面的循环中增长到一个巨大的数字。)

因此,设置b[i] = rand()%60;x[i] = rand()%60;可以缓解此问题。

设置b[i] = rand()%6; x[i] = rand()%6;a[n*i+j] = rand()%6可以最终满足更严格的退出循环条件。

有趣的是,建立更大的对角优势(将a[n*i+i] = temp+1'更改为a[n*i+i] = temp+10;也会使程序收敛,而之前不会。

我不熟悉其他人描述的ULP条件,但会投入一些时间

如果未来的读者有时间和精力,也许他们应该阅读"What Every Computer Scientist Should Know About Floating-Point Arithmetic",即使我没有。

BTW,xorg用于存储原始的x向量,用于调试目的,因为我在编写CodeBlocks时非常困难

感谢所有人的贡献。