2010-07-26 7 views
5

Mientras trabajaba en un ejercicio de programación simple, produje un ciclo while (lazo DO en Fortran) que estaba destinado a salir cuando una variable real había alcanzado un valor preciso.¿Fortran tiene limitaciones inherentes a la precisión numérica en comparación con otros lenguajes?

Noté que debido a la precisión que se usaba, la igualdad nunca se cumplía y el ciclo se hacía infinito. Por supuesto, esto no es desconocido y se aconseja a uno que, en lugar de comparar dos números para la igualdad, lo mejor es ver si la diferencia absoluta entre dos números es menor que un umbral establecido.

Lo que encontré decepcionante fue lo bajo que tuve que establecer este umbral, incluso con variables con doble precisión, para que mi ciclo salga correctamente. Además, cuando reescribí una versión "destilada" de este ciclo en Perl, no tuve problemas con la precisión numérica y el ciclo salió bien.

Dado que el código para producir el problema es tan pequeño, tanto en Perl y Fortran, me gustaría reproducir aquí en caso de que estoy pasando por alto un detalle importante:

Código Fortran

PROGRAM precision_test 
IMPLICIT NONE 

! Data Dictionary 
INTEGER :: count = 0 ! Number of times the loop has iterated 
REAL(KIND=8) :: velocity 
REAL(KIND=8), PARAMETER :: MACH_2_METERS_PER_SEC = 340.0 

velocity = 0.5 * MACH_2_METERS_PER_SEC ! Initial Velocity 
DO 
     WRITE (*, 300) velocity 
     300 FORMAT (F20.8) 
     IF (count == 50) EXIT 
     IF (velocity == 5.0 * MACH_2_METERS_PER_SEC) EXIT 
!  IF (abs(velocity - (5.0 * MACH_2_METERS_PER_SEC)) < 1E-4) EXIT 
     velocity = velocity + 0.1 * MACH_2_METERS_PER_SEC 
     count = count + 1 
END DO 

END PROGRAM precision_test 

Código Perl línea de

#! /usr/bin/perl -w 
use strict; 

my $mach_2_meters_per_sec = 340.0; 

my $velocity = 0.5 * $mach_2_meters_per_sec; 

while (1) { 
     printf "%20.8f\n", $velocity; 
     exit if ($velocity == 5.0 * $mach_2_meters_per_sec); 
     $velocity = $velocity + 0.1 * $mach_2_meters_per_sec; 
} 

La salida comentado en Para tran es lo que necesitaría para que el ciclo salga normalmente. Tenga en cuenta que el umbral está configurado en 1E-4, lo que me parece bastante patético.

Los nombres de las variables provienen del ejercicio de programación basado en el autoaprendizaje que estaba realizando y no tienen ninguna relevancia.

La intención es que el bucle se detiene cuando la variable de velocidad alcanza 1700.

Aquí son las salidas truncadas:

Perl salida

170.00000000 
    204.00000000 
    238.00000000 
    272.00000000 
    306.00000000 
    340.00000000 

...

1564.00000000 
    1598.00000000 
    1632.00000000 
    1666.00000000 
    1700.00000000 

salida Fortran

170.00000000 
    204.00000051 
    238.00000101 
    272.00000152 
    306.00000203 
    340.00000253 

...

1564.00002077 
    1598.00002128 
    1632.00002179 
    1666.00002229 
    1700.00002280 

Qué buena es la velocidad de Fortran y facilidad de paralelización si su exactitud apesta? Me recuerda a las tres formas de hacer las cosas:

  1. la manera correcta

  2. la manera incorrecta

  3. El Power Max Camino

"¿No es que solo ¿la forma incorrecta?"

" ¡Sí! Pero más rápido!"

Bromas aparte, debo estar haciendo algo mal.

¿El Fortran tiene limitaciones inherentes en la precisión numérica en comparación con otros idiomas, o soy yo (muy probable) el culpable?

Mi compilador es gfortran (gcc versión 4.1.2), Perl v5.12.1, en un doble núcleo AMD Opteron @ 1 GHz.

+0

¿No debería el # 3 ser "El modo de máxima potencia"? – detly

+0

Whoops. Tienes razón. ¡Gracias! – EMiller

+1

Fortran está usando un doble aquí y sospecho que Perl hará lo mismo. Pero buscar un igual con un número de punto flotante es pedir problemas (a menos que tengas un número "seguro" como '0.0144'). Entonces diría que probablemente sea tu método de prueba lo que está mal. – Wolph

Respuesta

15

Su tarea es convertir accidentalmente el valor de precisión simple y luego de vuelta a doblar.

Tr y haciendo que su 0.1 * sea 0.1D0 * y deba solucionar su problema.

+0

En realidad, estaba usando un REAL (tipo = 8), que es DOBLE PRECISIÓN. – Gilead

+1

@Gilead: Sí, me lo perdí inicialmente; mi experiencia Fortran no es con GNU Fortran. Se eliminó la respuesta incorrecta y se ingresó la respuesta correcta. – ysth

+0

¡Ah! Esa es la respuesta. ¡Muy sutil! +1 por esto. – Gilead

7

Como ya se ha respondido, las constantes de punto flotante "simples" en Fortran se predeterminarán al tipo real predeterminado, que probablemente será de precisión simple. Este es un error casi clásico.

Además, usar "kind = 8" no es portátil, le dará una precisión doble con gfortran, pero no con otros compiladores. La forma segura y portátil de especificar precisiones para variables y constantes en Fortran> = 90 es usar las funciones intrínsecas y solicitar la precisión que necesita. Luego especifique "tipos" en las constantes donde la precisión es importante. Un método conveniente es definir sus propios símbolos. Por ejemplo:

integer, parameter :: DR_K = selected_real_kind (14) 

REAL(DR_K), PARAMETER :: MACH_2_METERS_PER_SEC = 340.0_DR_K 

real (DR_K) :: mass, velocity, energy 

energy = 0.5_DR_K * mass * velocity**2 

Esto también puede ser importante para enteros, por ejemplo, si se necesitan valores grandes. Para preguntas relacionadas con números enteros, vea Fortran: integer*4 vs integer(4) vs integer(kind=4) y Long ints in Fortran

+0

'real (tipo = 8)' es un Fortran 90 perfectamente estándar. Todos los compiladores compatibles con Fortran 90 deben, y lo hacen, respaldarlo. Dicho esto, la forma correcta de solicitar una precisión dada en Fortran 90 es algo como lo siguiente: 'entero, parámetro :: dp = selected_real_kind (15, 307) real (kind = dp) :: a' Ver [ Fortran Wiki] (http://fortranwiki.org/fortran/show/Real+precision) –

+3

@fB: @MSB es correcto para afirmar que kind = 8 no es portable, y tiene razón al afirmar que es estándar Fortran 90 La no portabilidad de la declaración surge porque mientras kind = 8 es un selector de clases válido, la interpretación del 8 depende de la implementación. Ciertamente, la mayoría de los compiladores actuales (creo) interpretan que se trata de un entero de 8 bytes, pero esto no está garantizado por el estándar. Ciertamente no siempre ha sido cierto que todos los compiladores de Fortran interpretarían esto de la misma manera, y una de las preocupaciones del programador promedio de Fortran es la portabilidad a lo largo del tiempo. –

+0

@MSB. De acuerdo, de hecho, comenté la respuesta incorrecta. –

Cuestiones relacionadas