代码之家  ›  专栏  ›  技术社区  ›  ricab

如果没有未定义的行为[c++],哪些浮点值不能转换为int?

  •  8
  • ricab  · 技术社区  · 8 年前

    我刚刚从C++14标准中读到这篇文章(我的重点):

    4.9浮点积分转换

    1. 浮点类型的prvalue可以转换为整数类型的prvalue。转换截断;也就是说,舍弃分数部分。 如果截断的值不能 在目标类型中表示。 [...]

    这让我思考

    1. 如果有的话, float 值无法表示为 int 在截断之后?(这是否取决于实施情况?)
    2. 如果有,这是否意味着 auto x = static_cast<int>(float) 是否不安全?
    3. 转换的正确/安全方式是什么 浮动 内景 那么(假设您想要截断)?
    2 回复  |  直到 6 年前
        1
  •  7
  •   Mike Vine    8 年前

    不久前我们遇到了这个问题,我手动制作了一些表,这些表在各种转换为各种大小的整数的边缘具有精确的浮点位模式。注:假设iee754为4字节 floats 和8字节 doubles 和2的补码有符号整数( int32_t 共4个字节,并且 int64_t 共8个字节)。

    如果需要将位模式转换为浮点或双倍,则需要键入pun或 memcpy 他们

    要回答你的问题,任何太大而无法放入目标整数的东西都是UB on conversion,而截断为零的唯一时间是 double -&燃气轮机; int32\u t . 因此,使用以下值,您可以将浮动与相关的最小/最大值进行比较,并且仅当它们在范围内时才进行转换。

    请注意,使用 INT_MIN / INT_MAX (或其现代极限对应项)进行交叉转换然后进行比较并不总是有效,因为这些大小值的浮点精度非常低。

    Inf/NaN在转换时也是UB。

    // float->int64 edgecases
    static const uint32_t FloatbitsMaxFitInt64 = 0x5effffff; // [9223371487098961920] Largest float which still fits int an signed int64
    static const uint32_t FloatbitsMinNofitInt64 = 0x5f000000; // [9223372036854775808] the bit pattern of the smallest float which is too big for a signed int64
    static const uint32_t FloatbitsMinFitInt64 = 0xdf000000; // [-9223372036854775808] Smallest float which still fits int an signed int64
    static const uint32_t FloatbitsMaxNotfitInt64 = 0xdf000001; // [-9223373136366403584] Largest float which to small for a signed int64
    
    // float->int32 edgecases
    static const uint32_t FloatbitsMaxFitInt32 = 0x4effffff; // [2147483520] the bit pattern of the largest float which still fits int an signed int32
    static const uint32_t FloatbitsMinNofitInt32 = 0x4f000000; // [2147483648] the bit pattern of the smallest float which is too big for a signed int32
    static const uint32_t FloatbitsMinFitInt32 = 0xcf000000; // [-2147483648] the bit pattern of the smallest float which still fits int an signed int32
    static const uint32_t FloatbitsMaxNotfitInt32 = 0xcf000001; // [-2147483904] the bit pattern of the largest float which to small for a signed int32
    
    // double->int64 edgecases
    static const uint64_t DoubleBitsMaxFitInt64 = 0x43dfffffffffffff; // [9223372036854774784] Largest double which fits into an int64
    static const uint64_t DoubleBitsMinNofitInt64 = 0x43e0000000000000; // [9223372036854775808] Smallest double which is too big for an int64
    static const uint64_t DoubleBitsMinFitInt64 = 0xc3e0000000000000; // [-9223372036854775808] Smallest double which fits into an int64
    static const uint64_t DoubleBitsMaxNotfitInt64 = 0xc3e0000000000001; // [-9223372036854777856] largest double which is too small to fit into an int64
    
    // double->int32 edgecases[when truncating(round towards zero)]
    static const uint64_t DoubleBitsMaxTruncFitInt32 = 0x41dfffffffffffff; // [~2147483647.9999998] Largest double that when truncated will fit into an int32
    static const uint64_t DoubleBitsMinTruncNofitInt32 = 0x41e0000000000000; // [2147483648.0000000] Smallest double that when truncated wont fit into an int32
    static const uint64_t DoubleBitsMinTruncFitInt32 = 0xc1e00000001fffff; // [~2147483648.9999995] Smallest double that when truncated will fit into an int32
    static const uint64_t DoubleBitsMaxTruncNofitInt32 = 0xc1e0000000200000; // [2147483649.0000000] Largest double that when truncated wont fit into an int32
    
    // double->int32 edgecases [when rounding via bankers method(round to nearest, round to even on half)]
    static const uint64_t DoubleBitsMaxRoundFitInt32 = 0x41dfffffffdfffff; // [2147483647.5000000] Largest double that when rounded will fit into an int32
    static const uint64_t DoubleBitsMinRoundNofitInt32 = 0x41dfffffffe00000; // [~2147483647.5000002] Smallest double that when rounded wont fit into an int32
    static const uint64_t DoubleBitsMinRoundFitInt32 = 0xc1e0000000100000; // [-2147483648.5000000] Smallest double that when rounded will fit into an int32
    static const uint64_t DoubleBitsMaxRoundNofitInt32 = 0xc1e0000000100001; // [~2147483648.5000005] Largest double that when rounded wont fit into an int32
    

    因此,对于您的示例,您希望:

    if( f >= B2F(FloatbitsMinFitInt32) && f <= B2F(FloatbitsMaxFitInt32))
        // cast is valid.
    

    其中B2F类似于:

    float B2F(uint32_t bits)
    {
        static_assert(sizeof(float) == sizeof(uint32_t), "Weird arch");
        float f;
        memcpy(&f, &bits, sizeof(float));
        return f;
    }
    

    请注意,此转换正确地拾取了NAN/inf(因为与它们的比较是错误的) 除非 您正在使用编译器的非iee754模式(例如,gcc上的ffast math或msvc上的/fp:fast)

        2
  •  4
  •   anatolyg    8 年前

    这一点也不奇怪 float 值超出 int 范围浮点值被发明来充分表示非常大(也非常小)的值。

    1. INT_MAX + 1 (通常等于 2147483648 )不能由表示 内景 ,但可以表示为 浮动 .
    2. static_cast<int>(float) 与未定义的行为一样不安全。然而 x + y 对于足够大的整数 x y 也是UB,所以这里也没什么大惊喜。
    3. 正确的方法取决于应用程序,就像在C++中一样。Boost具有 numeric_cast 在溢出时引发异常;这可能对你有好处。进行饱和(将太大的值转换为 INT_MIN INT_MAX ),编写如下代码

      float f;
      int i;
      ...
      if (static_cast<double>(INT_MIN) <= f && f < static_cast<double>(INT_MAX))
          i = static_cast<int>(f);
      else if (f < 0)
          i = INT_MIN;
      else
          i = INT_MAX;
      

      然而,这并不理想。您的系统有 double 可以表示的最大值的类型 内景 ? 如果是,它将起作用。此外,您希望如何精确舍入接近以下值的最小值或最大值: 内景 ? 如果您不想考虑此类问题,请使用 boost::numeric_cast ,如上所述 here .