2010-07-26 11 views
5

Während ich an einer einfachen Programmierübung arbeitete, erstellte ich eine while-Schleife (DO-Schleife in Fortran), die beendet werden sollte, wenn eine reelle Variable einen genauen Wert erreicht hatte.Hat Fortran inhärente Einschränkungen der numerischen Genauigkeit im Vergleich zu anderen Sprachen?

Ich bemerkte, dass aufgrund der Präzision, die verwendet wurde, die Gleichheit nie erreicht wurde und die Schleife unendlich wurde. Dies ist natürlich nicht unüblich und man wird darauf hingewiesen, dass man, anstatt zwei Zahlen für die Gleichheit zu vergleichen, am besten sehen sollte, ob die absolute Differenz zwischen zwei Zahlen kleiner als ein festgelegter Schwellenwert ist.

Was ich enttäuschend fand, war, wie niedrig ich diesen Grenzwert einstellen musste, sogar mit Variablen mit doppelter Genauigkeit, damit meine Schleife richtig beendet wurde. Außerdem, als ich eine "destillierte" Version dieser Schleife in Perl umschrieb, hatte ich keine Probleme mit der numerischen Genauigkeit und die Schleife endete gut.

Da der Code das Problem zu erzeugen, so klein ist, sowohl in Perl und Fortran, würde Ich mag es für den Fall, hier zu reproduzieren ich ein wichtiges Detail am Beschönigung:

Fortran-Code

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 

Perl-Code

#! /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; 
} 

Die kommentierten-out Linie in Für tran ist, was ich für die Schleife verwenden müsste, um normal zu beenden. Beachten Sie, dass der Schwellenwert auf 1E-4 eingestellt ist, was meiner Meinung nach ziemlich erbärmlich ist.

Die Namen der Variablen stammen aus der selbstlernbasierten Programmierübung, die ich durchgeführt habe, und haben keine Relevanz.

Die Absicht ist, dass die Schleife beendet, wenn die Geschwindigkeitsgröße erreicht 1700.

Hier sind die trunkierten Ausgänge:

Perl Output

170.00000000 
    204.00000000 
    238.00000000 
    272.00000000 
    306.00000000 
    340.00000000 

...

1564.00000000 
    1598.00000000 
    1632.00000000 
    1666.00000000 
    1700.00000000 

Fortran Ausgabe

170.00000000 
    204.00000051 
    238.00000101 
    272.00000152 
    306.00000203 
    340.00000253 

...

1564.00002077 
    1598.00002128 
    1632.00002179 
    1666.00002229 
    1700.00002280 

Was nützt Geschwindigkeit und Leichtigkeit der Parallelisierung des Fortran, wenn seine Genauigkeit stinkt? Erinnert mich an die drei Möglichkeiten, Dinge zu tun:

  1. Der richtige Weg

  2. The Wrong Way

  3. Die Max Power Way

„Ist das nicht nur der falsche Weg?"

" Ja! Aber schneller!“

Alle beiseite ein Scherz, ich muss etwas falsch machen.

Does Fortran haben inhärente Beschränkungen auf numerische Genauigkeit im Vergleich zu anderen Sprachen, oder bin ich (sehr wahrscheinlich) die eine Schuld?

Mein Compiler ist gfortran (gcc Version 4.1.2), Perl v5.12.1, auf einem Dual-Core AMD Opteron @ 1 GHZ.

+0

Sollte nicht # 3 "Max Power" sein? – detly

+0

Whoops. Du hast recht. Vielen Dank! – EMiller

+1

Fortran benutzt hier ein Double und ich vermute, dass Perl dasselbe tut. Aber die Suche nach einem Gleichheitszeichen mit einer Fließkommazahl verlangt nach Problemen (es sei denn, Sie haben eine "sichere" Zahl wie "0.0144"). Also würde ich sagen, dass es wahrscheinlich Ihre Testmethode ist, die falsch ist. – Wolph

Antwort

15

Ihre Zuordnung versehentlich den Wert einfacher Genauigkeit konvertiert und dann zurückgehen zu.

Tr Machen Sie Ihre 0.1 *0.1D0 * und Sie sollten Ihr Problem behoben sehen.

+0

Eigentlich benutzte er eine REAL (Art = 8), was DOUBLE PRECISION ist. – Gilead

+1

@Gilead: Ja, das habe ich anfangs verpasst; Meine Fortran Erfahrung ist nicht mit GNU Fortran. Entfernte schlechte Antwort und legte korrekte Antwort ein. – ysth

+0

Ah! Das ist die Antwort. Sehr subtil! +1 dafür. – Gilead

7

Wie bereits beantwortet, werden "einfache" Gleitkommakonstanten in Fortran standardmäßig auf den reellen Standardtyp gesetzt, der wahrscheinlich eine einfache Genauigkeit hat. Dies ist ein fast klassischer Fehler.

Auch die Verwendung von "kind = 8" ist nicht tragbar - es gibt Ihnen doppelte Genauigkeit mit gfortran, aber nicht mit einigen anderen Compilern. Die sichere, portable Methode zum Festlegen von Genauigkeiten für Variablen und Konstanten in Fortran> = 90 besteht darin, die systeminternen Funktionen zu verwenden und die erforderliche Genauigkeit anzufordern. Dann spezifizieren Sie "Arten" auf den Konstanten, in denen Präzision wichtig ist. Eine bequeme Methode besteht darin, eigene Symbole zu definieren. Zum Beispiel:

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 

Dies kann auch für Ganzzahlen wichtig sein, z. B. wenn große Werte benötigt werden. Für verwandte Fragen zu Ganzzahlen siehe Fortran: integer*4 vs integer(4) vs integer(kind=4) und Long ints in Fortran

+0

'real (kind = 8)' ist perfekt Standard Fortran 90. Alle Fortran 90 kompatiblen Compiler sollten und unterstützen es. Das heißt, die richtige Art, in Fortran 90 nach einer bestimmten Genauigkeit zu fragen, ist etwa so: 'integer, parameter :: dp = selected_real_kind (15, 307) real (kind = dp) :: a' Siehe die [ Fortran Wiki] (http://fortranwiki.org/fortran/show/Real+precision) –

+3

@fB: @MSB ist richtig zu behaupten, dass Art = 8 ist nicht tragbar, und Sie haben Recht zu sagen, dass es Standard Fortran 90 ist Die Nichttragbarkeit der Aussage ergibt sich, weil, während Art = 8 ein gültiger Artselektor ist, die Interpretation der 8 von der Implementierung abhängig ist. Sicherlich werden die meisten aktuellen Compiler (glaube ich) dies als eine 8-Byte-Ganzzahl interpretieren, aber dies ist nicht durch den Standard garantiert. Es ist sicherlich nicht immer wahr, dass alle Fortran-Compiler dies auf die gleiche Weise interpretieren würden, und eines der Probleme des durchschnittlichen Fortran-Programmierers ist die Portabilität über die Zeit hinweg. –

+0

@MSB Richtig du bist, ich habe tatsächlich die falsche Antwort kommentiert. –