Есть ли у Фортрана присущие ограничения численной точности по сравнению с другими языками? - PullRequest
5 голосов
/ 26 июля 2010

Работая над простым упражнением по программированию, я создал цикл while (цикл DO в Фортране), который должен был завершиться, когда реальная переменная достигла точного значения.

Я заметил, что из-за используемой точности, равенство никогда не было достигнуто, и цикл стал бесконечным. Это, конечно, не неслыханно, и рекомендуется, чтобы вместо сравнения двух чисел на равенство лучше было бы увидеть, если абсолютная разница между двумя числами меньше установленного порога.

Что меня разочаровало, так это то, как низко я должен был установить этот порог, даже с переменными с двойной точностью, чтобы мой цикл корректно завершился. Кроме того, когда я переписал «перегнанную» версию этого цикла в Perl, у меня не было проблем с числовой точностью, и цикл вышел нормально.

Поскольку код для создания проблемы настолько мал, как в Perl, так и в 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

Код Perl

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

Закомментированная строка в Фортране - это то, что мне нужно было бы использовать для нормального завершения цикла. Обратите внимание, что пороговое значение установлено на 1E-4, что, на мой взгляд, довольно жалко.

Имена переменных взяты из упражнения по самостоятельному программированию, которое я выполнял, и не имеют никакого отношения.

Намерение состоит в том, что цикл останавливается, когда переменная скорости достигает 1700.

Вот усеченные выводы:

Выход Perl

    170.00000000
    204.00000000
    238.00000000
    272.00000000
    306.00000000
    340.00000000

...

   1564.00000000
   1598.00000000
   1632.00000000
   1666.00000000
   1700.00000000

Выход Fortran

    170.00000000
    204.00000051
    238.00000101
    272.00000152
    306.00000203
    340.00000253

...

   1564.00002077
   1598.00002128
   1632.00002179
   1666.00002229
   1700.00002280

Что хорошего в скорости и простоте распараллеливания Фортрана, если его точность воняет? Напоминает мне о трех способах делать вещи:

  1. Правильный путь

  2. Неправильный путь

  3. Путь максимальной мощности

"Разве это не неправильный путь?"

«Да! Но быстрее!»

Шутки в сторону, должно быть, я что-то делаю не так.

Есть ли у Фортрана присущие ограничения численной точности по сравнению с другими языками, или я (вполне вероятно) виноват?

Мой компилятор - gfortran (gcc версии 4.1.2), Perl v5.12.1, на двухъядерном процессоре AMD Opteron @ 1 ГГц.

Ответы [ 2 ]

15 голосов
/ 26 июля 2010

Ваше задание случайно конвертирует значение в одинарную точность, а затем обратно в удвоение.

Попробуйте сделать 0.1 * равным 0.1D0 *, и вы должны увидеть, что ваша проблема исправлена.

7 голосов
/ 26 июля 2010

Как уже ответили, «простые» константы с плавающей точкой в ​​Фортране по умолчанию будут иметь реальный тип по умолчанию, который, вероятно, будет иметь одинарную точность. Это почти классическая ошибка.

Кроме того, использование «kind = 8» непереносимо - оно даст вам двойную точность с gfortran, но не с некоторыми другими компиляторами. Безопасный, переносимый способ задания точности для переменных и констант в Fortran> = 90 - это использовать встроенные функции и запрашивать необходимую точность. Затем укажите «виды» для констант, где важна точность. Удобный способ - определить свои собственные символы. Например:

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

Это также может быть важно для целых чисел, например, если необходимы большие значения. Для связанных вопросов для целых чисел см. Фортран: целое число * 4 против целого числа (4) против целого числа (род = 4) и Длинные целые числа в Фортране

...