2016-10-11 6 views
1

Я пытаюсь использовать внутреннюю функцию «ПОТОЛОК», но ошибка округления затрудняет получение того, что я хочу иногда. Пример кода просто очень прост:Используйте ПОТОЛОК без эффекта округления Ошибка

PROGRAM MAIN 

    IMPLICIT NONE 

    INTEGER, PARAMETER  ::  ppm_kind_double = KIND(1.0D0) 
    REAL(ppm_kind_double)  ::  before,after,dx 

    before = -0.112 
    dx = 0.008 
    after = CEILING(before/dx) 

    WRITE(*,*) before, dx, before/dx, after 

    END 

И я получил результаты: enter image description here

значение я даю «до» и «ах» в коде только для демонстрации. Например, для тех, кто до/dx = -13.5, я хочу использовать ПОТОЛОК для получения -13. Но для картины, которую я показываю, я действительно хочу получить -14. Я рассмотрел некоторые аргументы, такие как

IF(ABS(NINT(before/dx) - before/dx) < 0.001) 

Но это просто не красиво. Есть ли лучший способ сделать это?

Update:

я с удивлением обнаружил, что проблема не будет возникать, если я установить переменные константы в ppm_kind_double. Поэтому я предполагаю, что эта «ошибка округления» произойдет только тогда, когда число цифр для точности округления машины, которую я использую, больше, чем указано в ppm_kind_double. Я фактически запускаю свою программу (не этот демонстрационный код) в кластере, чего я не знаю о точности машины. Так может быть, это четкость на этой машине, что приводит к этой проблеме?

После того как я установить константы двойной точности:

before = -0.112_ppm_kind_double 
dx = 0.008_ppm_kind_double 

enter image description here

+0

Вы отбросили меня с желанием -14. Потребовалось некоторое время, чтобы понять, что -0.112/0.008 ровно -14, но из-за ошибок округления результат несколько больше, а «ПОТОЛОК» затем усиливает эту минусовую ошибку. – chw21

+0

@ chw21, на самом деле я видел ваш предыдущий комментарий и ждал, пока вы его найдете. :) – Ruizhi

+2

Данная точность является частью проблемы здесь, стоит отметить, что ваши литеральные константы являются единственной точностью - например, '-0.112' должно быть написано' -0.112_ppm_kind_double'. Однако это изменение не решит основной проблемы. – IanH

ответ

0

Это немного сложнее, потому что вы никогда не знаете, где ошибка округления приходит. Если dx был всего лишь чуть больше, чем 0.008, тогда деление before/dx все равно может быть округлено до того же значения, но теперь -13 будет правильным ответом.

Тем не менее, самый распространенный метод, который я видел, - это просто nudge предыдущее значение, когда-либо так мало в противоположном направлении. Что-то вроде этого:

program sign_test 
    use iso_fortran_env 
    implicit none 
    real(kind=real64) :: a, b 
    integer(kind=int32) :: c 
    a = -0.112 
    b = 0.008 
    c = my_ceiling(a/b) 
    print*, a, b, c 
contains 
    function my_ceiling(v) 
     implicit none 
     real(kind=real64), intent(in) :: v 
     integer(kind=int32) :: my_ceiling 
     my_ceiling = ceiling(v - 1d-6, kind=int32) 
    end function my_ceiling 
end program sign_test 

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

+0

Спасибо chw21, дайте первоначальное предположение, что разумный сдвиг также является хорошим способом. – Ruizhi

0

примечание если ваши реалов являются умозрительно «точно» до заданной точности вы могли бы сделать что-то вроде этого:

after=nint(1000*before)/nint(1000*dx) 

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