为什么同一个程序的 c 和 fortran 版本会产生不同的结果?
Why the c and fortran versions of this same program produce different results?
我用的是gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
c代码为:
// Compile with:
// gcc -o little_c little.c
#include <stdio.h> // printf
void main(void) {
int n = 800;
float a[n][n], b[n][n], c[n][n];
int i, j, k;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
a[i][j] = (float) (i+j);
b[i][j] = (float) (i-j);
}
}
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
float t = (float) 0.0;
for (k = 0; k < n; k++)
t += a[i][k] * a[i][k] + b[k][j] * b[k][j];
//t += a[i][k] + b[k][j]; // If I comment the above line and uncomment this, the c and fortran reults are the same
c[i][j] = t;
}
}
printf("%f", c[n-1][n-1]); // prints the very last element
}
Fortran 代码:
! Compile with:
! gfortran -o little_fort little.f90
program little
implicit none
integer, parameter :: n = 800
real :: a(n,n), b(n,n), c(n,n)
real :: t
integer :: i, j, k ! Counters
do i = 1, n
do j = 1, n
a(i,j) = real(i-1+j-1) ! Minus one, for it to be like the c version
b(i,j) = real(i-1-(j-1)) ! because in c, the index goes from 0 to n-1
end do
end do
do i = 1, n
do j = 1, n
t = 0.0
do k = 1, n
t = t + a(i,k) * a(i,k) + b(k,j) * b(k,j)
!t = t + a(i,k) + b(k,j) ! If I comment the above line and uncomment this, the c and fortran reults are the same
end do
c(i,j) = t
end do
end do
write (*,"(F20.4)") c(n,n) ! This is the same as c[n-1][n-1] in c
end program little
c程序打印:1362136192.000000
Fortran 程序打印:1362137216.0000
如果我不将每个元素与自身相乘,正如我在代码中的注释中所述,我将在两个版本的程序中得到相同的值:
c 程序:639200.000000
Fortran 程序:639200.0000
为什么当我使用乘法时,c 和 Fortran 代码会产生不同的结果?。是否必须使用不同的实数实现方式?
不同之处在于求值顺序和浮点类型的有限精度。
如果将 Fortran 版本更改为
t = t + (a(i,k) * a(i,k) + b(k,j) * b(k,j))
即使用 a
和 b
在术语周围添加括号,两种语言的结果相同。由于使用了 +=
赋值运算符,C 版本已使用此计算顺序。
如评论中所述,这是可用精度限制下的预期行为。
当我编写程序的 Ada 版本时,我发现我必须将小数精度降低到 6 位小数才能获得 Fortran 答案。
Ada 版本是:
和Ada.Text_IO;使用 Ada.Text_Io;
procedure Main is
type Real is digits 6;
package Real_IO is new Ada.Text_IO.Float_IO(Real);
use Real_IO;
subtype Index is integer range 1..800;
type Matrix is array(Index, Index) of Real;
A : Matrix;
B : Matrix;
C : Matrix;
T : Real := 0.0;
begin
for I in Index loop
for J in Index loop
A(I,J) := Real(I - 1 + J - 1);
B(I,J) := Real(I - 1 - (J - 1));
end loop;
end loop;
for I in Index loop
for J in Index loop
T := 0.0;
for K in Index loop
T := T + A(I,K) * A(I,K) + B(K,J) *B(K,J);
end loop;
C(I,J) := T;
end loop;
end loop;
Put(Item => C(Index'Last, Index'Last), Exp => 0, Aft => 4);
New_Line;
end Main;
行定义类型Real定义浮点类型的精度:
type Real is digits 6;
使用六位精度产生的值是
1362137216.0000
使用更高精度的浮点类型产生值
1362135200.0000
我用的是gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
c代码为:
// Compile with:
// gcc -o little_c little.c
#include <stdio.h> // printf
void main(void) {
int n = 800;
float a[n][n], b[n][n], c[n][n];
int i, j, k;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
a[i][j] = (float) (i+j);
b[i][j] = (float) (i-j);
}
}
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
float t = (float) 0.0;
for (k = 0; k < n; k++)
t += a[i][k] * a[i][k] + b[k][j] * b[k][j];
//t += a[i][k] + b[k][j]; // If I comment the above line and uncomment this, the c and fortran reults are the same
c[i][j] = t;
}
}
printf("%f", c[n-1][n-1]); // prints the very last element
}
Fortran 代码:
! Compile with:
! gfortran -o little_fort little.f90
program little
implicit none
integer, parameter :: n = 800
real :: a(n,n), b(n,n), c(n,n)
real :: t
integer :: i, j, k ! Counters
do i = 1, n
do j = 1, n
a(i,j) = real(i-1+j-1) ! Minus one, for it to be like the c version
b(i,j) = real(i-1-(j-1)) ! because in c, the index goes from 0 to n-1
end do
end do
do i = 1, n
do j = 1, n
t = 0.0
do k = 1, n
t = t + a(i,k) * a(i,k) + b(k,j) * b(k,j)
!t = t + a(i,k) + b(k,j) ! If I comment the above line and uncomment this, the c and fortran reults are the same
end do
c(i,j) = t
end do
end do
write (*,"(F20.4)") c(n,n) ! This is the same as c[n-1][n-1] in c
end program little
c程序打印:1362136192.000000
Fortran 程序打印:1362137216.0000
如果我不将每个元素与自身相乘,正如我在代码中的注释中所述,我将在两个版本的程序中得到相同的值:
c 程序:639200.000000
Fortran 程序:639200.0000
为什么当我使用乘法时,c 和 Fortran 代码会产生不同的结果?。是否必须使用不同的实数实现方式?
不同之处在于求值顺序和浮点类型的有限精度。
如果将 Fortran 版本更改为
t = t + (a(i,k) * a(i,k) + b(k,j) * b(k,j))
即使用 a
和 b
在术语周围添加括号,两种语言的结果相同。由于使用了 +=
赋值运算符,C 版本已使用此计算顺序。
如评论中所述,这是可用精度限制下的预期行为。
当我编写程序的 Ada 版本时,我发现我必须将小数精度降低到 6 位小数才能获得 Fortran 答案。
Ada 版本是:
和Ada.Text_IO;使用 Ada.Text_Io;
procedure Main is
type Real is digits 6;
package Real_IO is new Ada.Text_IO.Float_IO(Real);
use Real_IO;
subtype Index is integer range 1..800;
type Matrix is array(Index, Index) of Real;
A : Matrix;
B : Matrix;
C : Matrix;
T : Real := 0.0;
begin
for I in Index loop
for J in Index loop
A(I,J) := Real(I - 1 + J - 1);
B(I,J) := Real(I - 1 - (J - 1));
end loop;
end loop;
for I in Index loop
for J in Index loop
T := 0.0;
for K in Index loop
T := T + A(I,K) * A(I,K) + B(K,J) *B(K,J);
end loop;
C(I,J) := T;
end loop;
end loop;
Put(Item => C(Index'Last, Index'Last), Exp => 0, Aft => 4);
New_Line;
end Main;
行定义类型Real定义浮点类型的精度:
type Real is digits 6;
使用六位精度产生的值是
1362137216.0000
使用更高精度的浮点类型产生值
1362135200.0000