将整数转换为浮点类型时如何检测溢出

6
C++依赖于C标准进行这些操作。据我所知,C标准有以下部分:
当整数类型的值转换为实浮点类型时,如果要转换的值在新类型中可以准确地表示,则它不变。如果要转换的值在可表示但无法准确表示的值范围内,则结果是选择最接近的较高或较低可表示值,以一种实现定义的方式选择。如果要转换的值超出了可以表示的值范围,则行为是未定义的。
有没有办法检查最后一种情况?在我的理解中,这种最后的未定义行为是不可避免的。如果我有一个整数值并且愚昧地检查类似于
i <= FLT_MAX

除了与精度相关的其他问题,我将首先触发它,因为比较首先将i转换为(在这种情况下或通常情况下转换为任何其他浮点类型),因此如果超出范围,我们会得到未定义的行为。

或者是否有一些关于整数和浮点类型相对大小的保证,可以暗示类似于“float始终可以表示int的所有值(当然不一定是精确的)”或至少是“long double始终可以容纳所有内容”,以便我们可以在该类型中进行比较?虽然我找不到任何像那样的东西。

这主要是一个理论练习,因此我对“在大多数架构上,这些转换总是有效”的答案不感兴趣。让我们尝试找到一种方法,在不假设C(++)标准之外的任何事情的情况下检测这种溢出! :)


2
64位IEEE浮点数可以表示任何<=32位整数而不会失去精度,而32位IEEE浮点数可以表示任何<=16位整数。我不确定是否有其他保证。一般来说,具有N位或更多有效数字的浮点类型可以精确地表示任何N位或更少的整数类型。 - cdhowie
3
这个问题很有趣。我认为它无法用一般的方式来解决。首先,标准甚至没有强制要求浮点数使用IEEE 754标准。我认为你最终会得到一个特定实现的逻辑,它知道浮点数是如何表示的以及它们的值是什么。 - SergeyA
1
@cdhowie 不要忘记浮点数尾数比实际存储的位数多1,因为规格化尾数的最高位始终为“1”,因此被隐含(除了值为“0”)。 - Weather Vane
2
@old_timer 如果使用宽整数和窄浮点数的代码中使用 uintmax_t 作为一个128位类型,其值仅略大于 FLT_MAX(溢出),这是一种真实的可能性,并且对于OP来说是一个合理的关注点。 - chux - Reinstate Monica
1
@curiousguy,你没有完整地阅读陈述。你不能通过整数转换溢出浮点数。尽管我使用了半精度来演示,但是使用典型的整数(32位、64位)从单精度开始,你不会溢出。你可能会失去精度,但这是另一回事,这是人们困惑的关键所在。引用的文本很容易理解,以及导致它的情况...但实现定义如人所料,否则他们将不得不编写数百页的内容... - old_timer
显示剩余14条评论
2个回答

4
检测将整数转换为浮点类型时的溢出情况。根据C规范,FLT_MAX和DBL_MAX至少为1E+37,因此所有值的|值|小于122位的整数在所有符合条件的平台上都可以转换为float而不会溢出。双精度浮点数也是如此。
要解决128/256等位数的整数的一般情况,需要同时减小FLT_MAX和some_big_integer_MAX。可以通过对两个数取对数来实现(bit_count()是一个待定用户代码)。
if(bit_count(unsigned_big_integer_MAX) > logbf(FLT_MAX)) problem();

如果整数没有填充,则可以这样做。
if(sizeof(unsigned_big_integer_MAX)*CHAR_BIT > logbf(FLT_MAX)) problem();

注意:使用类似logbf()的FP函数可能会在精确整数计算中产生边缘情况,导致比较错误。
宏魔法可以使用像下面这样的晦涩测试,利用BIGINT_MAX肯定是2的幂减1和FLT_MAX除以2的幂肯定是精确的(除非FLT_RADIX == 10)。
如果从大整数类型到float的转换对于某些大整数不精确,则此预处理器代码将抱怨
#define POW2_61 0x2000000000000000u  
#if BIGINT_MAX/POW2_61 > POW2_61
  // BIGINT is at least a 122 bit integer 
  #define BIGINT_MAX_PLUS1_div_POW2_61  ((BIGINT_MAX/2 + 1)/(POW2_61/2))
  #if BIGINT_MAX_PLUS1_div_POW2_61 > POW2_61
    #warning TBD code for an integer wider than 183 bits
  #else
    _Static_assert(BIGINT_MAX_PLUS1_div_POW2_61 <= FLT_MAX/POW2_61, 
        "bigint too big for float");
  #endif
#endif

[编辑2]

有没有办法检查最后一种情况?

如果从大整数类型转换为float在选定的大整数中是不精确的,则此代码将报错

当然,在尝试转换之前需要进行测试。鉴于各种舍入模式或FLT_RADIX == 10很少出现,现在可以轻松获得的是一个略有下降趋势的测试。当它为真时,转换将起作用。但是,只有非常小范围的大整数在以下测试中报告错误才能正确转换。

以下是我需要再考虑一下的更完善的想法,但我希望它提供了一些关于测试 OP所寻找的编码思路。

#define POW2_60 0x1000000000000000u
#define POW2_62 0x4000000000000000u
#define MAX_FLT_MIN 1e37
#define MAX_FLT_MIN_LOG2 (122 /* 122.911.. */)

bool intmax_to_float_OK(intmax_t x) {
#if INTMAX_MAX/POW2_60 < POW2_62
  (void) x;
  return true; // All big integer values work
#elif INTMAX_MAX/POW2_60/POW2_60 < POW2_62
  return x/POW2_60 < (FLT_MAX/POW2_60) 
#elif INTMAX_MAX/POW2_60/POW2_60/POW2_60 < POW2_62
  return x/POW2_60/POW2_60 < (FLT_MAX/POW2_60/POW2_60) 
#else
  #error TBD code
#endif
}

@Julian Kniephoff,你的帖子还需要更多的帮助吗? - chux - Reinstate Monica

1
这是一个C++模板函数,它返回适合两种给定类型的最大正整数。
template<typename float_type, typename int_type>
int_type max_convertible()
{
    static const int int_bits = sizeof(int_type) * CHAR_BIT - std::is_signed<int_type>() ? 1 : 0;
    if ((int)ceil(std::log2(std::numeric_limits<float_type>::max())) > int_bits)
        return std::numeric_limits<int_type>::max();
    return (int_type) std::numeric_limits<float_type>::max();
}

如果你要转换的数字比这个函数返回的值大,就无法进行转换。不幸的是,我很难找到一组类型来测试它,很难找到一个整数类型,它不能适合最小的浮点类型。

“最大可表示整数” <-- 我相信你的意思是它返回最大可表示的整数,适合于给定类型,所有较小的整数也是可表示的并适合于给定类型。 - cdhowie
@cdhowie 我已经修改了那个陈述的措辞。我真的需要明确说明更小的整数也适用吗?这似乎应该是自动的。 - Mark Ransom
我不认为它是正确的。我确信有更大的整数可以被准确地表示,但它们不是连续的整数。 - cdhowie
@cdhowie 好的,现在我明白你的意思了。问题指定了三个不同的范围:所有连续值都可以表示的范围,可能会出现一些舍入误差的范围,以及导致未定义行为的范围。只有第三种情况需要回答,这就是答案。 - Mark Ransom
公正的观点,由于某种原因我以为问题是在问第二种情况。糟糕。 - cdhowie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接