0

这是我的代码:

int counter = 0;
float h = 1.0f;
float phase = 0.0f;
const float hrate = 1.0f / (100.0f * (h * h) + 0.02f);
while (!(phase > 100.0f))
{
    phase += hrate;
    counter++;
}
std::cout << counter << std::endl;

我需要对其进行微调以输出这些counters,对于每个h(使用我的实际 pc/编译器):

  h   counter
0.0   3      
0.2   403    
0.4   1602   
0.5   2502   
0.6   3602   
0.8   6402   
1.0   10002 

相反,它给了我这个:

  h   counter
0.0   3
0.2   402     << different
0.4   1602
0.5   2502
0.6   3602
0.8   6403    << different
1.0   10004   << different
4

1 回答 1

1

首先,如果你需要依赖这些行为,你想static_assertis_iec559. 这应该确保所有浮点计算产生确定性的结果。

您还想确保您没有使用-ffast-math或类似的开关。我相信行为良好的编译器不应该报告iec559何时启-ffast-math用,但目前情况并非如此。

#include <iostream>
#include <limits>
#include <cfenv>

static_assert(std::numeric_limits<float>::is_iec559, "");

void test_it(float h)
{
    int counter = 0;
    float phase = 0.0f;
    const float hrate = 1.0f / (100.0f * (h * h) + 0.02f);
    while (!(phase > 100.0f))
    {
        phase += hrate;
        counter++;
    }
    std::cout << counter << std::endl;
}

一旦我们解决了这个问题,我们要注意浮点舍入模式。理论上这应该默认为FE_TONEAREST,但是如果你有一个大的代码库,则不能保证其他代码会弄乱它(舍入模式和所有浮点环境都是线程本地状态)。

int main()
{
#pragma STDC FENV_ACCESS ON
    std::fesetround(FE_TONEAREST);
    test_it(0.2f); // 402
    test_it(0.8f); // 6403
    test_it(1.0f); // 10004

    std::fesetround(FE_DOWNWARD);
    test_it(0.2f); // 403
    test_it(0.6f); // 6403
    test_it(1.0f); // 10005

    std::fesetround(FE_UPWARD);
    test_it(0.2f); // 402
    test_it(0.6f); // 6402
    test_it(1.0f); // 10001
}

我自己没有进行数学计算,但两者都gcc同意clang这些结果。

最后,请注意常量表达式总是使用 FE_TONEAREST 计算的。您的原始代码“硬编码” 的值h。这可能会导致hrate持续评估(使用 FE_TONEAREST),这可能会影响结果。

于 2022-02-03T18:36:12.357 回答