2

I use gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

The c code is:

// 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 code:

! 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

The c program prints: 1362136192.000000

and the Fortran program prints: 1362137216.0000

If I do not multiply each element by itself, as I state in the comments in the code, I get the same value for both versions of the program:

c prigram: 639200.000000

Fortran program: 639200.0000

Why when I use a multiplication the c and Fortran code produce different results?. Does it have to be with different implementations of the real numbers?

Antonio Serrano
  • 385
  • 2
  • 15
  • 1
    The results differ at the 7th significant digit which is about par for `float`. I suggest you use `double` instead of `float` everywhere unless there is a very good reason not to. Please see [Why Are Floating Point Numbers Inaccurate?](https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate) – Weather Vane Nov 11 '20 at 16:32
  • 1
    I'm not sure, but is it possible one is using SSE, and one is using x87 math, which have different precisions? Can you disassemble the inner loop and see if they're using different FP subsystems? Also, float is only 32 bit precision, and I'm not sure what real does by default. – Max Nov 11 '20 at 16:33
  • 1
    Just putting this here : [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – John Alexiou Nov 11 '20 at 16:45
  • In line with what Max said, if you should set compile options to permit sse optimization, the Fortran is more likely to speed up and get slightly more accurate results, due to your use of stride 1 inner loops. An alternative to the partial use of parentheses to make the Fortraersrersn evaluation more like C would be to use the SUM intrinsic, as there no longer are compilers restricted to Fortran 77. – tim18 Nov 13 '20 at 11:22

2 Answers2

6

The difference is due to the order of evaluation combined with the limited precision of the floating point type.

If you change the Fortran version to

t = t + (a(i,k) * a(i,k) + b(k,j) * b(k,j))

i.e. add parenthesis around the terms with a and b, you get the same result for both languages. The C version already uses this order of evaluation due to the use of the += assignment operator.

As mentioned in the comments, this is expected behavior at the limits of the available precision.

rtoijala
  • 1,200
  • 10
  • 20
  • 1
    Alternately, changing the line in the C version to `t += b[k][j] * b[k][j] + a[i][k] * a[i][k];` gets the same result as the FORTRAN version, in my C implementation (Apple Clang 11, with `-O3`). I suspect there might be multiple ways of evaluating that get the same result. – Eric Postpischil Nov 11 '20 at 16:41
  • 1
    Just a note to _op_, that addition and most importantly subtraction of two quantities of different magnitudes introduces significant round-off errors. – John Alexiou Nov 11 '20 at 16:43
0

When I wrote an Ada version of the program I found that I had to reduce the decimal precision to 6 decimals to achieve the Fortran answer.

The Ada version is:

with Ada.Text_IO; use 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;

The line defining type Real defines the precision of the floating point type:

type Real is digits 6;

The value produced using six digits of precision is

1362137216.0000

Use of higher precision floating point types resulted in the value

1362135200.0000
Jim Rogers
  • 4,822
  • 1
  • 11
  • 24
  • So, what is the answer to the question? – Vladimir F Героям слава Nov 11 '20 at 18:36
  • The answer is the difference can be caused by a number of factors including different round-off errors due to different evaluation order as well as round off errors resulting from reduced precision floating point numbers. – Jim Rogers Nov 11 '20 at 18:48
  • 1
    But both Fortran and C in the question use single precision. What is the point demonstrating it will be different in double precision in a different language? It is totally obvious it will be different in the double precision in any language. – Vladimir F Героям слава Nov 11 '20 at 18:57
  • I used the GNAT Ada compiler, which is part of the GNU toolchain. It should produce the same results as the GNU C++ compiler. Both languages share the same compiler back end. – Jim Rogers Nov 11 '20 at 21:56