为什么“八度”舍入到“比0早1个”?

时间:2019-02-12 19:06:17

标签: floating-point octave rounding data-representation

上下文:

在Octave中,我为Sigmoid函数编写了代码,该函数返回0到1之间的值;在理想情况下,对于-Inf只会返回0,对于+ Inf只会返回1,但是由于浮点数的不精确性,与这两个值极其接近的值都经过了四舍五入。

问题:

我的问题是为什么会发生以下情况:取整的边界明显不同于0:1:

>> sigmoid(-709)
ans =   1.2168e-308
>> sigmoid(-710)
ans = 0
>> sigmoid(36)
ans =  1.00000
>> sigmoid(37)
ans =  1
>> (sigmoid(37)-1)==0
ans = 1
>> (sigmoid(36)-1)==0
ans = 0
>> sigmoid(-710)==0
ans = 1
>> sigmoid(-709)==0
ans = 0

在该示例中,您可以看到将输出四舍五入所需的值的幅度要比四舍五入至0所需的值小得多。与-710相比,37的差异非常大,考虑到它们应该相同。幅度但符号相反...

我的代码:

也许这是我的功能出现的问题:

function [z] = sigmoid(x)
z = 1.0 ./(1.0+exp(-x));
endfunction

我尝试过的事情:

另一点是,我更改了将结果加1的函数(本质上将图形向上平移了1),并且边界分别变为2和1的+/- 37-这使我认为它确实是尤其要处理0,而不仅要处理函数及其下限。

如果这与我的计算机有关,那么会导致什么呢?

1 个答案:

答案 0 :(得分:9)

首先,查看this brilliant answer by gnovice on floating-point representation

在此之后,让我们看看您在这里看到的内容:您可以计算出非常接近零的值:sigmoid(-709)大约等于1.2e-308,但是您无法计算出类似地接近一个值:sigmoid(709)等于1,而不是1 - 1.2e-308,甚至是sigmoid(36) == 1,而不是略小于1的值。

但是,当我们知道浮点数如何存储在内存中时,我们将意识到1 - 1.2e-308无法准确表示。我们需要308个十进制数字才能准确表示该数字。双精度浮点数(八度中的默认值)大约有15个十进制数字。也就是说,1 - 1e-16可以表示,但是1 - 1e-17无法表示。

eps(1)的值为2.2204e-16,这是我们可以在双精度浮点数中编码的与1的最小差。

但是可以更精确地表示接近0的值:eps(0)4.9407e-324。这是因为诸如1.2e-308之类的值不需要表示308个十进制数字,而仅需2个,其指数为-308。

无论如何,如果您依赖于Sigmoid函数的确切值,而该值与转换位置相距甚远,那么您的代码逻辑就会出问题。

如果要使该函数对称,您所能做的就是降低低端的精度。有两种方法可以做到这一点:

  1. 仅将非常小的值设置为零,以便在另一侧与z==0的同一点到达z==1

    function z = sigmoid(x)
      z = 1.0 ./ (1.0+exp(-x));
      z(z < eps(1)) = 0;
    end
    
  2. 始终计算函数的右半部分,然后折回以获得负输入。这使得x=0两侧的计算错误是对称的:

    function z = sigmoid(x)
      z = 1.0 ./ (1.0+exp(-abs(x)));
      I = x < 0;
      z(I) = 0.5 - z(I);
    end