2016-12-24 79 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 

}