可由小于 1 的浮点类型表示的最大值

Largest value representable by a floating-point type smaller than 1

提问人:0xbadf00d 提问时间:3/7/2022 最后编辑:Adrian Mole0xbadf00d 更新时间:3/9/2022 访问量:2147

问:

有没有办法获得由小于 的浮点类型表示的最大值。float1

看到了以下定义

static const double DoubleOneMinusEpsilon = 0x1.fffffffffffffp-1;
static const float FloatOneMinusEpsilon = 0x1.fffffep-1;

但这真的是我们应该如何定义这些价值观吗?

根据该标准,是机器epsilon,即1.0与下一个值之间的差值,可由浮点类型表示。但这并不一定意味着定义会更好。std::numeric_limits<T>::epsilonTT(1) - std::numeric_limits<T>::epsilon

C++ 浮点 C++17 epsilon

评论

7赞 user17732522 3/7/2022
std::nextafter
1赞 vandench 3/7/2022
对于 IEEE-754,我相信您可以从浮点位的整数表示中减去一个,这本质上似乎是十六进制文字正在做的事情。gcc.godbolt.org/z/dechh5xxa
2赞 Marek R 3/8/2022
1.0 - DBL_EPSILON / 2
5赞 Jarod42 3/8/2022
什么最接近的双倍到1-0-那不是-1-0有关。
2赞 David Hammen 3/8/2022
@U.Windl:假设是IEEE 754二进制格式。介于 0.5(含)和 1.0(不含)之间的数字基本上由 0.5(以 10 为基数)乘以 1.xxxxxxx(以 2 为基数)表示,其中每个 x 都是 0 或 1。介于 1.0(含)和 2.0(不含)之间的数字基本上由 1.0 乘以 1.xxxxxxx 表示。最后一位单位 (ULP) 的值是介于 0.5 和 1.0 之间的数字与介于 1.0 和 2.0 之间的数字的一半。

答:

26赞 Adrian Mole 3/7/2022 #1

您可以使用 std::nextafter 函数,尽管它的名称,但它可以通过使用适当的参数来检索给定起点之前的下一个可表示值。(通常为 、 或 )。to-Infinity0+Infinity

根据 的定义,无论 C++ 实现使用哪种浮点格式,这都可以移植。(二进制与十进制,或尾数的宽度,又名 significand,或其他任何东西。nextafter

示例:检索类型的最接近值小于 1(在 Windows 上,使用 Visual Studio 2019 中的 clang-cl 编译器),答案与计算结果不同(如注释中所述,对于IEEE754数字不正确;低于 2 的任意幂,可表示数字之间的接近程度是上面的两倍):double1 - ε

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

int main()
{
    double naft = std::nextafter(1.0, 0.0);
    std::cout << std::fixed << std::setprecision(20);
    std::cout << naft << '\n';
    double neps = 1.0 - std::numeric_limits<double>::epsilon();
    std::cout << neps << '\n';
    return 0;
}

输出:

0.99999999999999988898
0.99999999999999977796

使用不同的输出格式,这可以打印为 和0x1.fffffffffffffp-10x1.ffffffffffffep-1 (1 - ε)


请注意,当使用类似技术确定大于 的最接近值时,调用将给出与计算相同的值 (1.000000000000000022204),正如 ε 的定义所预期的那样。1nextafter(1.0, 10000.)1 + ε


性能

C++23 要求 ,但目前只有一些编译器支持。GCC 确实通过它进行持续传播,但 clang 不能 (Godbolt)。如果您希望它像IEEE754 binary64 的系统一样快(启用优化),那么在某些编译器上,您必须等待 C++23 支持的那部分。(很有可能,一旦编译器能够做到这一点,就像GCC一样,即使没有实际使用,它们也会进行优化。std::nextafterconstexpr0x1.fffffffffffffp-1;double-std=c++23

const double DoubleBelowOne = std::nextafter(1.0, 0.);在全局范围内,最坏的情况是在启动时运行一次函数,在使用它的地方破坏常量传播,但在与其他运行时变量一起使用时,其性能与 FP 文本常量大致相同。

评论

4赞 Drew Dormann 3/7/2022
结果可能与 不同,因为 epsilon 被定义为与下一个较大值之间的距离。(很容易被误解或误用1 - ε1.0epsilon())
1赞 Adrian Mole 3/8/2022
@463035818_is_not_a_number是的 - 页面对两者之间的区别有点模糊。页面的“C”版本看起来更清晰一些,指定了转换为什么以及何时转换。long double
1赞 Marek R 3/8/2022
std::numeric_limits<double>::epsilon();给出 1.0 和下一个最小值之间的差值。因此,要获得比 1.0 更小的东西,应该提取 epsilon 的一半,因为与 相比,该值的指数小 1 。所以它应该是:1.0double neps = 1.0 - std::numeric_limits<double>::epsilon() * 0.5;
2赞 Adrian Mole 3/8/2022
@MarekR 是的,对于 IEEE-754 格式,这将起作用。但根据定义,该方法将为任何系统/架构/类型提供正确的答案。或者它应该。毫无疑问,它的实现方式将取决于目标架构所使用的系统。nextafter
4赞 Peter Cordes 3/8/2022
@Andrew:如果您只是抱怨测试程序的文本输出,我认为这足以转换为正确的 FP 位模式;当然,它并不完全可代表;这不是 FP -> 字符串函数的目标。当被函数读取时,它们的目标是精确到位的往返,该函数将字符串转换为双精度,并将正确的舍入转换为最接近。(这是非常不平凡的)。godbolt.org/z/9v1Gdf6zc 确认0x1.fffffffffp-10.99999999999999977796strtod
9赞 Eric Postpischil 3/8/2022 #2

这可以通过使用 C 标准中指定的浮点表示特征来计算,而无需调用函数。由于 提供刚好高于 1 的可表示数字之间的距离,并提供用于表示数字的基数,因此刚好低于 1 的可表示数字之间的距离除以该基数:epsilonradixepsilon

#include <iostream>
#include <limits>


int main(void)
{
    typedef float Float;

    std::cout << std::hexfloat <<
        1 - std::numeric_limits<Float>::epsilon() / std::numeric_limits<Float>::radix
        << '\n';
}

评论

1赞 Adrian Mole 3/8/2022
好!但是,说句迂腐的话,其实就是“调用一个函数”。:-)epsilon()
0赞 Eric Postpischil 3/8/2022
@AdrianMole:不要让我用、等代替它。FLT_EPSILONDBL_EPSILON
0赞 Adrian Mole 3/8/2022
呵呵。9-3(顺便说一句,已经显示了我的“批准”,所以不需要编辑。
-2赞 Andrew 3/8/2022 #3

0.999999940395355224609375 是小于 1 的最大 32 位浮点数。下面的代码对此进行了演示:

Mac_3.2.57$cat float2uintTest4.c 
#include <stdio.h>
int main(void){
    union{
        float f;
        unsigned int i;
    } u;
    //u.f=0.9999;
    //printf("as hex: %x\n", u.i); // 0x3f7fffff
    u.i=0x3f800000; // 1.0
    printf("as float: %200.200f\n", u.f);
    u.i=0x3f7fffff; // 1.0-e
          //00111111 01111111 11111111 11111111
          //seeeeeee emmmmmmm mmmmmmmm mmmmmmmm
    printf("as float: %200.200f\n", u.f);

    return(0);
}
Mac_3.2.57$cc float2uintTest4.c 
Mac_3.2.57$./a.out 
as float: 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
as float: 0.99999994039535522460937500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

评论

1赞 Adrian Mole 3/8/2022
假设 IEEE-754 表示,那么您的打印精度远远高于可以表示的精度(~ 7 或 8 位小数)。将我的答案的所有相关部分调整为类型将分别给出 和 的输出。float0.999999940395355224610.99999988079071044922
2赞 Eric Postpischil 3/8/2022
C++ 标准允许实现使用其他格式,包括使用二进制基础但编码与此答案假设不同的格式,以及使用非二进制基础的格式。float
3赞 Eric Postpischil 3/8/2022
@AdrianMole:“~ 7 或 8 位小数”不是浮点表示数字的正确模型。每个不是无穷大或 NaN 的浮点值都精确地表示一个有限数,精确到无限多个小数位。有 IEEE-754“单精度”数字(通常用于 的格式),其十进制数字有 105 位有效数字,并且它们被精确表示。将它们视为数字的近似值会导致编程错误。float
5赞 Eric Postpischil 3/8/2022
@prosfilaes:不,它们不是数字的近似值。IEEE-754 标准对此很明确:每个不是无穷大或 NaN 的浮点基准面都精确地表示一个实数。C 和 C++ 标准也这么说。在浮点运算中,不是数字是近似值,而是运算。这些运算被定义为返回将相应的实数算术运算结果四舍五入到以浮点格式表示的最接近的数字(可选择舍入规则)所得到的数字。
3赞 Eric Postpischil 3/8/2022
@prosfilaes:这个模型的数字是精确的,运算近似于实数运算,对于浮点运算至关重要:需要分析浮点运算、设计浮点运算、编写浮点运算证明以及调试浮点运算。