试图找到浮点数、双精度数、长双精度数的最小正数。适用于 double 和 long double,但不适用于浮点

Trying to find smallest positive number for float, double, long double. Works for double and long double but fails for float

提问人:CaptainParsifal 提问时间:10/9/2023 最后编辑:Adrian MoleCaptainParsifal 更新时间:10/11/2023 访问量:142

问:

我正在尝试为每种数据类型(浮点数、双精度数和长双精度数)找到最大和最小数字。我正在使用一个循环,我将每个值与无穷大(最大数字)和 0(最小数字)进行比较。然后,它打印找到的值。它为我提供了双精度和长双精度的正确值,浮点型中的最大数字也是正确的。while

但是,对于最小的浮点数,它打印 0。当我修改程序以显示循环中的每个值时,正确的值是倒数第二个,但之后,由于某种原因,循环再次进行,说 0 > 0 并将 0 打印为最小值。

我不明白,为什么会这样?即使最后一个值太小以至于四舍五入为 0,为什么它会决定 0 > 0 并让循环最后一次进行?

这是程序:

#include <iostream>
#include <cmath>

using namespace std;

int main()
{
    float big_f = 1, small_f = 1;
    double big_d = 1, small_d = 1;
    long double big_ld = 1, small_ld = 1;

    while(!(isinf(big_f*2)))
    {
        big_f *= 2;
    }

    while((small_f*0.5)>0 and !((small_f*0.5)==0))
    {
        small_f *= 0.5;
    }

    cout << "Najmensie cislo float je "<< small_f << endl;
    cout << "Najvacsie cislo float je "<< big_f << endl;

    while(!(isinf(big_d*2)))
    {
        big_d *= 2;
    }

    while((small_d*0.5)>0)
    {
        small_d *= 0.5;
    }

    cout << "Najmensie cislo double je "<< small_d << endl;
    cout << "Najvacsie cislo double je "<< big_d << endl;

    while(!(isinf(big_ld*2)))
    {
        big_ld *= 2;
    }

    while((small_ld*0.5)>0)
    {
         small_ld *= 0.5;
    }

     
    cout << "Najmensie cislo long double je "<< small_ld << endl;
    cout << "Najvacsie cislo long double je "<< big_ld << endl;
}

请注意,这是针对学校的,我不能只将其与.FLT_MIN

C++ while 循环 浮点 比较

评论

7赞 PaulMcKenzie 10/9/2023
我试图为每种数据类型找到最大和最小的数字——这一切都是由<限制>完成的。无需发明自己的代码即可执行此操作。
4赞 fabian 10/9/2023
将 1 乘以 1 会导致 被提升到 并且得到的表达式类型为 ,因此您不会使用 2 秒的比较,但实际上与floatdoublefloatdoubledoublefloat(small_f*0.5)>0(static_cast<double>(small_f) * 0.5) > 0
4赞 Pepijn Kramer 10/9/2023
使用浮点数时使用 0.0f、0.5f。0.0 和 0.5 是双精度。
3赞 PaulMcKenzie 10/9/2023
拜托,这是给学校的,我不能把它和FLT_MIN进行比较——我真的想知道为什么学校要浪费学生的时间,除非这是你正在学习的一些非常深入的“编译器内在”课程。此外,即使您让它“工作”,也要将您的答案与使用 .它们是一样的吗?如果不是,那么要么你的代码是错误的,要么你无法从编写和循环中获得真正的答案。std::numeric_limitswhilefor
5赞 user17732522 10/10/2023
由于次正常现象,您的一般方法不会给出正确的答案。即使您有适当的算法,也需要将所有表达式转换为正确的类型,并确保以正确的类型执行算术运算。然后,您需要注意,编译器被允许围绕浮点运算进行一些实现定义的和可能的非确定性优化,例如,允许它们使用比表达式的实际类型允许的更高精度的中间结果。

答:

2赞 Eric Postpischil 10/10/2023 #1

0.5是一个常数,因此转换为算术并用于算术。然后测试是否尚未达到 a 达到零的点。doublesmall_f*0.5small_fdoubledouble(small_f*0.5)>0small_fdouble

要解决此问题,请使用 .或者,使用 并让 转换为 .0.50.5ffloatsmall_f/2int2float

此外,C++ 标准允许实现在计算表达式时使用比名义类型更高的精度,因此,例如,即使 和 是,也可以用算术计算,或者可以用 即使 和 是 来计算。为了防止这种情况,请将值分配给对象或执行强制转换,这是“放弃”过高精度所必需的:。x*ydoublelong doublexyfloatlong doublexydouble(float) (small_f/2) > 0

评论

0赞 user17732522 10/10/2023
"为了防止这种情况,请将值分配给对象或执行强制转换,这是“丢弃”过高精度所必需的“: 请注意,默认情况下编译器可能无法正确遵循此处的标准。例如,使用默认设置的 GCC 会忽略强制转换的标准要求舍入。 或者必须给出一个标志 (not ) 才能遵循此处的标准。同样,默认情况下,GCC 在语句之间执行浮点表达式收缩,因此可能也需要。我还没有测试过 Clang/MSVC。-fexcess-precision=standard-std=c++XX-std=gnu++XX-ffp-contract=off
0赞 user17732522 10/10/2023
此外,我猜如何丢弃多余的精度应该取决于舍入模式。所以我不确定它是否保证四舍五入到预期的地方。0
0赞 TooTone 10/10/2023 #2

这并不能直接回答你的问题,但是如果我试图了解浮点数,我会看看它们是如何表示的:特别是IEEE格式--参见 https://en.wikipedia.org/wiki/IEEE_754,看看我是否可以通过受规范启发的编码来获得答案。浮点数本质上是符号 * 分数 * 指数,在进行数值工作时,理解这一点确实非常有帮助。

例如,为了获得最高的浮点数,我写了这个。FLT_MAX

float largest_floating_point()
{
    const unsigned mantissa_index = 0;
    const unsigned exponent_index = 23;
    const unsigned sign_index = 31;

    // maximise mantissa
    // E.g. here we subtract 1 from binary 1,00000000 to get binary 0,11111111
    const unsigned mantissa = (1 << (exponent_index - mantissa_index)) - 1;
    // maximise exponent
    const unsigned num_nan_bits = 1;
    const unsigned exponent = (1 << (sign_index - exponent_index)) - 1 - num_nan_bits;
    // sign is zero for largest number
    const unsigned sign = 0;

    // combine by shifting and ORing bits together
    const unsigned bit_pattern =
        (mantissa << mantissa_index) | (exponent << exponent_index) | (sign << sign_index);

    // cast to float -- go via array of bytes to avoid arithmetic conversion
    static_assert(sizeof(unsigned) == sizeof(float));
    union
    {
        unsigned u;
        float f;
    } n;
    n.u = bit_pattern;
    return n.f;
}

这不符合你使用循环的方法,也不是人们想要在生产中使用的程序,尤其是因为严格来说,铸造是未定义的。但这适用于 Visual C++ (Windows) 和 gcc (linux)。

我从中学到了很多东西,也从编写代码(未显示)中学到了很多东西。我已经编程了很长时间,并认为我非常了解浮点表示。事实证明我没有,:)学到了一些东西。FLT_MIN

如果您选择探索这种方法以获得限制,请仔细注意规范,特别是主要文章中的“偏差”,NaN 的表示方式 (https://en.wikipedia.org/wiki/NaN) 和零的表示方式 (https://en.wikipedia.org/wiki/Signed_zero)。

评论

0赞 user17732522 10/10/2023
"但这适用于 Visual C++ (Windows) 和 gcc (linux)“:它在没有标志的 GCC 上无效。默认情况下,GCC 将使用类型别名限制进行优化。-fno-strict-aliasing
0赞 TooTone 10/10/2023
@user17732522事实证明,gcc 版本的构建失败是——我认为代码也会产生错误的数字。关闭错误警告后,最小和最大浮点数的测试通过。不过,我还是要谈谈混叠问题——几年前我就必须把这个问题做好,并且完全忘记了它有多尴尬。'array' is used uninitialized
0赞 user17732522 10/10/2023
该警告是 GCC 内部分析的结果,该分析确定无法写入,因为它正在 上运行,而不是从中读取。GCC 不仅会使用该扣除来生成诊断,还会用于优化。array((unsigned*)array)[0] = bit_pattern;unsignedfloat
0赞 TooTone 10/10/2023
至少在我的机器上,gcc 会产生警告,编译后的代码会产生我想要的结果;然而,这显然是不安全的。但是,答案的重点是学习浮点表示 -- 锯齿和编译器优化是另一回事 -- 当我在重要的代码上工作时,我学到了足够多的东西,意识到我真正知道的很少。
1赞 dimich 10/10/2023
union { uint32_t u; float f; } n; n.u = 1; printf("%Le\n", (long double)n.f);