这些双精度值如何精确到20位小数?

时间:2016-01-06 18:11:08

标签: fortran precision

我正在测试一些非常简单的等价错误,当精度问题并且希望以扩展的双精度执行操作时(这样我知道答案将在~19位数内)然后在double中执行相同的操作精度(第16位会出现舍入误差),但不知怎的,我的双精度算术保持了19位精度。

当我在扩展的double中执行操作时,然后将数字硬编码到另一个Fortran例程中,我得到了预期的错误,但是当我在这里为一个双精度变量分配一个扩展的双精度变量时,是否会发生一些奇怪的事情? / p>

program code_gen
    implicit none 
    integer, parameter :: Edp = selected_real_kind(17)
    integer, parameter :: dp = selected_real_kind(8)
    real(kind=Edp) :: alpha10, x10, y10, z10 
    real(kind=dp) :: alpha8, x8, y8, z8

    real(kind = dp) :: pi_dp = 3.1415926535897932384626433832795028841971693993751058209749445

    integer :: iter
    integer :: niters = 10

    print*, 'tiny(x10) = ', tiny(x10)
    print*, 'tiny(x8)  = ', tiny(x8)
    print*, 'epsilon(x10) = ', epsilon(x10)
    print*, 'epsilon(x8)  = ', epsilon(x8)

    do iter = 1,niters
        x10 = rand()
        y10 = rand()
        z10 = rand()
        alpha10 = x10*(y10+z10)

        x8 = x10 
        x8 = x8 - pi_dp
        x8 = x8 + pi_dp
        y8 = y10 
        y8 = y8 - pi_dp
        y8 = y8 + pi_dp
        z8 = z10 
        z8 = z8 - pi_dp
        z8 = z8 + pi_dp
        alpha8 = alpha10

        write(*, '(a, es30.20)') 'alpha8 .... ', x8*(y8+z8)
        write(*, '(a, es30.20)') 'alpha10 ... ', alpha10

        if( alpha8 .gt. x8*(y8+z8) ) then
            write(*, '(a)') 'ERROR(.gt.)'
        elseif( alpha8 .lt. x8*(y8+z8) ) then
            write(*, '(a)') 'ERROR(.lt.)'
        endif
    enddo
end program code_gen

其中rand()是找到here的gfortran函数。

如果我们只谈论一种精确类型(例如,取双倍),那么我们可以将机器epsilon表示为E16,大约为2.22E-16。如果我们简单地添加两个实数x+y,那么结果机器表示的数字为(x+y)*(1+d1),其中abs(d1) < E16。同样,如果我们将该数字乘以z,则结果值实际为(z*((x+y)*(1+d1))*(1+d2)),其中(z*(x+y)*(1+d1+d2))几乎为abs(d1+d2) < 2*E16。如果我们现在转向扩展双精度,那么唯一改变的是E16变为E20并且值大约为1.08E-19

我希望以扩展的双精度执行分析,以便我可以比较应该相等的两个数字但是有时候,舍入错误会导致比较失败。通过赋值x8=x10,我希望创建扩展双精度值x10的双精度'版本',其中只有x8的前16位数符合{的值{ {1}},但在打印出值时,它显示所有20个数字都相同,并且没有按照我的预期发生预期的双精度舍入误差。

还应该注意的是,在尝试之前,我编写了一个程序,它实际上写了另一个程序,其中包含x10x和{{1}的值是'硬编码'到20位小数。在此版本的程序中,yz的比较失败了,但我无法通过将扩展的双精度值作为双精度变量来复制相同的失败。

为了进一步“扰乱”双精度值并添加舍入误差,我从我的双精度变量中添加了.gt.,然后减去了.lt.,这应该留下剩余变量的一些双精度舍入误差,但是我在最后的结果中仍然没有看到这一点。

1 个答案:

答案 0 :(得分:6)

作为链接状态的gfortran文档,rand的函数结果是默认的实数值(单精度)。这样的值可以由您的每个其他真实类型精确表示。

即,x10=rand()为扩展精度变量x10分配单个精度值。它完全如此。现在存储在x10中的相同值被分配给双精度变量x8,但这仍然可以表示为双精度。

使用double和extended类型的计算返回相同的值时,single-as-double有足够的精度。 [见本答复末尾的注释。]

如果您希望看到精度损失的实际效果,请先使用扩展或双精度值。例如,使用内在rand

而不是使用random_number(返回单个精度值)。
call random_number(x10)

(具有标准Fortran的优点)。与函数不同,函数在(几乎)所有情况下都返回值类型而不管值的最终用法,此子例程将为您提供与参数对应的精度。你(希望)会从你的&#34;硬编码&#34;中看到很多。实验

或者,正如agentp评论的那样,以双精度值

开始可能更直观
call random_number(x8); x10=x8   ! x8 and x10 have the precision of double precision
call random_number(y8); y10=y8
call random_number(z8); z10=z8

并从该起点开始计算:然后这些额外的位将开始显示。

总而言之,当您执行x8=x10时,您获得的x8的前几位与x10的位相对应,但其中许多位和{{1}后面的位都是零。

当涉及到x10扰动时,您再次将单精度(此时为文字常量)值分配给双精度变量。只是拥有所有这些数字并不能使它成为默认真实文字以外的任何数字。您可以使用pi_dp后缀指定其他类型的文字,如其他答案中所述。

最后,还要担心编译器对regards to optimization的作用。

我的论文是从单精度值开始,所执行的计算完全可以用双精度和扩展精度表示(具有相同的值)。对于其他计算,或者从具有更多位设置或表示的起点(例如,在某些系统或其他编译器上,具有类型_Edp的数字类型可能具有完全不同的特征,例如不同的基数)情况就是这样。

虽然这很大程度上是基于猜测并希望它解释了观察结果。幸运的是,有很多方法可以测试这个想法。当我们谈论IEEE算法时,我们可以考虑不精确的标志。如果在计算过程中没有提出该标志,我们可以很高兴。

使用gfortran有一个编译选项selected_real_kind(17),它将使不精确的标志信号发出。使用gfortran 5.0,支持内部模块-ffpe=inexact,可以以便携/标准方式使用。

你可以考虑这个标志进行进一步的实验:如果它被提出,你可以看到两个精度之间的差异。