如何安全地执行从双精度到浮点的缩小转换?

How do I perform a narrowing conversion from double to float safely?

提问人:alrav 提问时间:11/16/2022 最后编辑:HolyBlackCatalrav 更新时间:11/16/2022 访问量:222

问:

当双精度缩小到浮点数时,我遇到了一些 -Wnarrowing 转换错误。我怎样才能以一种明确定义的方式做到这一点?最好使用模板中的选项,我可以切换以将行为从抛出异常切换到钳制到最接近的值或简单的截断。我正在查看转换,但似乎它只是在引擎盖下执行静态转换和比较跟进:了解 gsl::narrow 实现。我想要一些更健壮的东西,因为根据 C++ 程序员应该了解的所有常见未定义行为是什么? 如果该值在目标类型中不可表示,则为 UB。我也非常喜欢这个实现,但它也依赖于一个:是否可以优化一个 static_cast<float> from double,分配给 double?我不想为此使用boost。还有其他选择吗?如果这在 c++03 中工作是最好的,但 c++0x(实验性 c++11)也是可以接受的......如果真的需要,或者 11 个......gsl::narrowstatic_cast<>static_cast<>

因为有人问,这里有一个简单的玩具例子:

#include <iostream>

float doubleToFloat(double num) {
    return static_cast<float>(num);
}

int main( int, char**){
    double source = 1; // assume 1 could be any valid double value
    try{
        float dest = doubleToFloat(source);
        std::cout << "Source: (" << source << ") Dest: (" << dest << ")" << std::endl;
    }
    catch( std::exception& e )
    {
        std::cout << "Got exception error: " << e.what() << std::endl;
    }
}

我的主要兴趣是向doubleToFloat(...)添加错误处理和安全性,如果需要,还可以添加各种自定义异常。

C++ 11 类型转换 C++03

评论

0赞 Casey 11/16/2022
请将您的代码放在问题中。
0赞 alrav 11/16/2022
@Casey添加了一个示例
0赞 n. m. could be an AI 11/16/2022
A 不可表示,因为它的绝对值大于 。doublefloatFLT_MAX
0赞 alrav 11/16/2022
@n.m.是的,但我认为这还不够,不是吗?难道您还需要考虑精度损失吗?和负数,但我觉得一些 abs() 调用使一切都为正可能会解决这个问题......
1赞 Nelfeal 11/16/2022
@n.m.连续值之间有很多值。演示。难道它们不算作“不可代表”吗?doublefloat

答:

0赞 NGauthier 11/16/2022 #1

这取决于你所说的“安全”是什么意思。在大多数情况下,精度很可能会下降。是否要检测是否发生这种情况?断言,还是只是知道它并通知用户?

一种可能的解决方案是将双精度静态转换为浮点,然后返回双精度,并比较之前和之后。相等的可能性不大,但您可以断言精度的损失在您的容忍范围内。

float doubleToFloat(double a_in, bool& ar_withinSpec, double a_tolerance) 
{
    auto reducedPrecision = static_cast<float>(a_in);
    auto roundTrip = static_cast<double>(reducedPrecision);
    ar_withinSpec = (roundTrip < a_tolerance);
    return reducedPrecision;
}

评论

0赞 alrav 11/16/2022
这与 gsl 所做的类似,但我确信在静态强制转换期间调用 UB 好像精度有所损失,这意味着该值在目标类型中无法表示。也就是说,如果可以检测到这种情况并且没有以其他方式处理,则例外是可以接受的。我不想在这里使用断言。
0赞 Nelfeal 11/16/2022 #2

只要浮点类型可以存储无穷大(这是极有可能的),就不会出现未定义的行为。你可以测试你是否真的想确定。std::numeric_limits<float>::has_infinity

用于静音警告,如果要检查溢出,可以执行如下操作:static_cast

template <typename T>
bool isInfinity(T f) {
    return f == std::numeric_limits<T>::infinity()
        || f == -std::numeric_limits<T>::infinity();
}

float doubleToFloat(double num) {
    float result = static_cast<float>(num);
    if (isInfinity(result) && !isInfinity(num)) {
        // overflow happened
    }
    return result;
}

任何未溢出的值都将完全转换为两个最接近的值之一(可能是最接近的值)。您可以使用 std::fesetround 显式设置舍入方向。doublefloat

评论

0赞 alrav 11/16/2022
这似乎很有希望。不过,这对多线程安全吗?我在 std::fesetround 的链接中没有提到这一点。我可以指望这是本地线程吗?或者我是否需要额外的同步才能使用它?
1赞 Nelfeal 11/16/2022
我不明白你在问什么。更改是线程本地的吗?似乎是这样。线程安全吗?是的,这是一个纯粹的功能。fesetrounddoubleToFloat
0赞 alrav 11/16/2022
是的,我想知道 fesetround 更改是否是线程本地的。在这种情况下,我将接受这个答案。