【问题标题】:Adding smallest possible float to a float将最小可能的浮点数添加到浮点数
【发布时间】:2026-01-10 06:30:01
【问题描述】:

我想将浮点数的最小可能值添加到浮点数。因此,例如,我尝试这样做以获得 1.0 + 可能的最小浮点数:

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

但这样做之后,我得到以下结果:

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

我使用的是 Visual Studio 2015。为什么会发生这种情况?我能做些什么来绕过它?

【问题讨论】:

  • 你为什么感到惊讶?您添加的是 min,而不是 epsilon。
  • 我没有意识到有区别!我曾假设它们总是等价的。谢谢,这很有帮助。
  • @Matteo 回答?我对这个问题没有确切的理由。
  • float 的最小正值比 1 小 38 个数量级,为 1.175e-38。 float 类型只提供六位精度,因此将最小值加到 1 就等于加零。

标签: c++ c++11 floating-point


【解决方案1】:

如果您想要 1 之后的下一个可表示值,则有一个名为 std::nextafter 的函数,来自 &lt;cmath&gt; 标头。

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

它返回从第一个参数开始沿第二个参数方向的下一个可表示值。所以如果你想找到下一个低于 1 的值,你可以这样做:

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

将最小的正可表示值加到 1 不起作用,因为 1 和下一个可表示值之间的差大于 0 和下一个可表示值之间的差。

【讨论】:

  • std::numeric_limits&lt;float&gt;::min() 并不是最小的正可表示值;它是最小的正归一化值,因此次正态可以更低。
  • IIRC,大约一半的浮点位模式表示一个幅度小于1.0 的数字。指数字段的范围或多或少以0 为中心(表示尾数的2^0 = 1.0 乘数),在考虑了编码方式的偏差后,它使得将FP 位模式排序为整数实际上有效。请参阅 Bruce Dawson 关于浮点怪异东西的优秀系列文章,包括 this one about the representation
  • 请参阅this article 了解该系列 FP 文章中的目录。
  • 此外,如果您确实想朝一个方向或另一个方向发展,+/-Infinity 是 std::nextafter 的第二个很好的参数。它可能会更快,具体取决于实现如何检查围绕 +/- 0.0 的特殊情况。
【解决方案2】:

您观察到的“问题”是由于浮点运算的本质

在 FP 中,精度取决于比例;在值1.0 周围,精度不足以区分1.01.0+min_representable,其中min_representable 是大于零的最小可能值(即使我们只考虑最小的标准化数std::numeric_limits&lt;float&gt;::min() ...最小的非正规是另外几个数量级)。

例如对于双精度 64 位 IEEE754 浮点数,在 x=10000000000000000 (1016) 的范围内,无法区分 xx+1


分辨率随比例变化的事实正是“浮点”这个名称的原因,因为小数点“浮动”。相反,定点表示将具有固定的分辨率(例如,在单位以下有 16 个二进制数字时,您的精度为 1/65536 ~ 0.00001)。

例如在 IEEE754 32 位浮点格式中,符号 1 位,指数 8 位,尾数 31 位:


最小值eps 使得1.0f + eps != 1.0f 可用作FLT_EPSILONstd::numeric_limits&lt;float&gt;::epsilon 的预定义常量。另请参阅 machine epsilon on Wikipedia,其中讨论了 epsilon 与舍入误差的关系。

即epsilon 是您在此处所期望的最小值,添加到 1.0 时会有所不同。

这个更通用的版本(对于 1.0 以外的数字)在最后一位(尾数)称为 1 个单位。参见*的ULP article

【讨论】:

  • 我想问题的根源在于人们使用“浮点”(或只是“浮点”)这个词来表示“计算机中的非整数”而不考虑(甚至不知道)实际的浮动性质(即精度取决于比例)。
  • 正确。如果要做很多这类事情,花一些时间研究浮点背后的概念是个好主意。可能会出现许多“令人惊讶”的效果,尤其是对于不知情的用户。
  • eps 是 FLT_MIN 的错误名称epsFLOAT_EPSILON 的缩写,即the smallest number that makes a difference when added to 1.01.0 的最后一位(尾数)是 1 个单位(请参阅 ulp)。您所描述的是 epsilon 和 1 ULP 的概念,但问题是 eps=smallest possible value greater than zero
  • 通过编辑为您更正了这一点,因此我可以投票赞成这个非常好的答案。请查看您是否喜欢我添加到您的答案中的文字。
【解决方案3】:

min 是(规范化形式)浮点可以假定的最小非零值,即大约 2-126 (-126 是浮点允许的最小指数);现在,如果你把它加到 1 你仍然会得到 1,因为 float 只有 23 位尾数,所以这么小的变化不能用这样一个“大”数字表示(你需要一个 126 位尾数查看将 2-126 与 1) 相加的变化。

改为 1 的最小可能更改是 epsilon(所谓的机器 epsilon),实际上是 2-23 - 因为它会影响尾数的最后一位。

【讨论】:

  • std::numeric_limits&lt;float&gt;::min() 是最小的正标准化值。次常态可以更低。
  • @user2357112:我应该在我的个人资料中添加一个警告,说明“我所做的任何关于浮点的讨论都是在无视非规范化数字的情况下完成的,这是最好忽略的丑陋野兽”:-)
  • 缺乏次常态更难看。有了可用的次正规数,减去两个不相等的数字总是会得到一个非零的答案。如果没有可用的次正规,它就不会。
  • @plugwash:嗯,这很整洁。即使输入已经是非正规的,这也是正确的,因为它只是尾数上的整数数学。
【解决方案4】:

要将浮点值增加/减少可能的最小量,请使用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(v,std::numeric_limits<T>::infinity()) ;
}
template<typename T>
T next_below(const T& v){
    return std::nextafter(v,-std::numeric_limits<T>::infinity()) ;
}

int main(){
  std::cout << "eps   : "<<std::numeric_limits<double>::epsilon()<< std::endl; // gives eps

  std::cout << "after : "<<next_above(1.0) - 1.0<< std::endl; // gives eps (the definition of eps)
  std::cout << "below : "<<next_below(1.0) - 1.0<< std::endl; // gives -eps/2

  // Note: this is what next_above does:
  std::cout << std::nextafter(std::numeric_limits<double>::infinity(),
     std::numeric_limits<double>::infinity()) << std::endl; // gives inf

  // while this is probably not what you need:
  std::cout << std::nextafter(std::numeric_limits<double>::infinity(),
     std::numeric_limits<double>::max()) << std::endl; // gives 1.79769e+308

}

【讨论】:

    最近更新 更多