2016-12-24 77 views
59

我想將浮點數的最小可能值添加到浮點數。因此,舉例來說,我想這樣做是爲了得到1.0 +可能的最小浮動:將最小可能浮點數添加到浮點數

float result = 1.0f + std::numeric_limits<float>::min(); 

但這樣做,我得到下面的結果後:我使用Visual Studio 2015年

(result > 1.0f) == false 
(result == 1.0f) == true 

。爲什麼會發生這種情況?我能做些什麼來解決它?

+27

你爲什麼感到驚訝?您正在添加min,而不是epsilon。 –

+6

我沒有意識到有一個區別!我曾假設他們總是等同的。謝謝,這很有幫助。 – Squidy

+1

@Matteo答案?這個問題我沒有真正的理由。 –

回答

86

如果您想要1後面的下一個可表示值,那麼將會有一個名爲std::nextafter的函數,該函數來自<cmath>標頭。

float result = std::nextafter(1.0f, 2.0f); 

它從第一個參數開始向第二個參數的方向返回下一個可表示的值。所以,如果你想找到低於1的下一個值,你可以這樣做:

float result = std::nextafter(1.0f, 0.0f); 

添加的最小正表示值1不起作用,因爲1和一個可表示的值之間的差大於大0與下一個可表示值之間的差值。

+18

'std :: numeric_limits :: min()'不是最小的正表示值;它是最小的正值歸一化值,所以子正常值可能會更低。 – user2357112

+2

IIRC,大約一半的浮點比特模式代表一個小於'1.0'的數字。指數字段的範圍或多或少以'0'爲中心(代表尾數的'2^0 = 1.0'乘數),在考慮了編碼方式中的偏差後,將FP位模式排序爲整數實際上工作。請參閱Bruce Dawson關於浮點奇怪內容的優秀系列文章,包括[關於代表性的這一篇](https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format /) –

+0

請參閱[本文](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)以獲取該系列FP文章中的目錄。 –

20

min是(歸一化形式)float可以假定的最小非零值,即大約2 -126(-126是浮點的最小允許指數);現在,如果你總結爲1,你仍然會得到1,因爲float只有23位尾數,所以這樣一個小的變化不能用這麼大的數字表示(你需要一個126位的尾數才能看到1的變化總和)。

的最小可能的改變爲1,相反,是epsilon(所謂機器最小),這實際上是2 -23 - 因爲它影響尾數的最後一個比特。

+2

'std :: numeric_limits :: min()'是最小的正*標準化*值。子異常可能會降低。 – user2357112

+0

@ user2357112:我應該在我的個人資料中添加一個警告,說明「我對浮點進行的任何討論都是無視非規範化的數字,這是醜陋的野獸最好忽略的」:-) –

+2

缺少低於正常值的是更醜陋的。如果可用的子正常減去兩個不相等的數字,將始終給出非零的答案。沒有低於正常值的情況下,它不會。 – plugwash

40

您正在觀察的「問題」是由於浮點運算的非常自然的

在FP中,精度取決於規模;圍繞價值1.0精度是不夠的,能夠1.01.0+min_representable其中min_representable可能是最小的值大於零(即使我們只考慮最小規格化數,std::numeric_limits<float>::min()更大的區分。最小的非正規的幅度的另一少量訂單小)。

例如與雙精度64位IEEE754浮點數字,周圍的x=10000000000000000規模(10 )這是不可能的xx+1之間進行區分。


事實上,帶有刻度的分辨率的變化是非常之所以命名爲「浮點」,因爲小數點「浮動」。一個固定點表示反而會有一個固定的分辨率(例如16個二進制數字低於你的單位精度爲1/65536〜0.00001)。

例如在IEEE754 32位浮點格式有一個比特的標誌,用於指數的8位和用於尾數31位:

floating point


的最小值eps,使得1.0f + eps != 1.0f可用作預定義的常數,如FLT_EPSILONstd::numeric_limits<float>::epsilon。另請參閱machine epsilon on Wikipedia,其中討論了epsilon與舍入錯誤的關係。

I.e. epsilon是您在此期待的最小值,在添加到1.0時有所作爲。

這個(對於1.0以外的數字)更通用的版本在尾數(尾數)被稱爲1個單位。請參閱維基百科的ULP article

+5

我想這個問題是植根於「浮點」(或者只是「浮點」)這個詞,用於「計算機中的非整數」而不考慮(或者甚至不知道)實際的浮動特性(即精度取決於按比例)。 –

+0

正確。如果有人要做這樣的事情,那麼花一些時間研究浮點背後的概念是個好主意。有一些「令人驚訝的」效果可能發生,特別是對於不知情的用戶。 –

+2

**'eps'是FLT_MIN **的錯誤名稱。 'eps'是'FLOAT_EPSILON'的縮寫,它是[添加到'1.0'時有所不同的最小數字](https://en.wikipedia.org/wiki/Machine_epsilon#Variant_definitions)。它的最後一個位置(尾數)爲1,單位爲'1.0'(見[ulp](https://en.wikipedia.org/wiki/Unit_in_the_last_place))。你所描述的是epsilon和1 ULP的概念,但問題是「eps =大於零的最小可能值」。 –

5

要將浮點值增加/減少最小可能值,請使用nextafter朝向+/- infinity()

如果您只是使用next_after(x,std::numeric_limits::max()),如果x是無窮大,結果將會出錯。

#include <iostream> 
#include <limits> 
#include <cmath> 

template<typename T> 
T next_above(const T& v){ 
    return std::nextafter(1.0,std::numeric_limits<T>::infinity()) ; 
} 
template<typename T> 
T next_below(const T& v){ 
    return std::nextafter(1.0,-std::numeric_limits<T>::infinity()) ; 
} 

int main(){ 
    std::cout << next_below(1.0) - 1.0<< std::endl; // gives eps 
    std::cout << next_above(1.0) - 1.0<< std::endl; // gives ~ -eps/2 

    // Note: 
    std::cout << std::nextafter(std::numeric_limits<double>::infinity(), 
    std::numeric_limits<double>::infinity()) << std::endl; // gives inf 
    std::cout << std::nextafter(std::numeric_limits<double>::infinity(), 
    std::numeric_limits<double>::max()) << std::endl; // gives 1.79769e+308 

}