【问题标题】:Comparing uint64_t and float for numeric equivalence比较 uint64_t 和 float 的数值等价性
【发布时间】:2015-09-27 17:11:35
【问题描述】:

我正在编写一个协议,它使用RFC 7049 作为其二进制表示。标准规定,如果它们的数值等于相应的 64 位数字,则协议可以使用数字的 32 位浮点表示。转换不得导致精度损失。

  • 哪些 32 位浮点数可以大于 64 位整数并在数值上与它们等价?
  • 比较float x; uint64_t y; (float)x == (float)y 是否足以确保值相等?这种比较会成立吗?

RFC 7049 §3.6. Numbers

就本规范而言,所有数字表示 对于相同的数值是等价的。这意味着一个 编码器可以将浮点值 0.0 编码为整数 0。 但是,这也意味着期望找到的应用程序 如果编码器,整数值只能找到浮点值 决定这些是可取的,例如当浮点值是 比 64 位整数更紧凑。

【问题讨论】:

  • What 32-bit float numbers can be bigger than 64-bit integer and numerically equivalent with them? 无:根据定义,等于Y 的数字X 不能大于Y
  • 只要 32 位浮点数是整数值,将其转换为 64 位整数就不会丢失精度。但是如果原来的浮点数不是整数,就会失去精度。
  • 当然这个比较可能是真的。 1.0f == float(1ull)
  • 这个问题只有在关于哪些 64 位整数可以表示为浮点数而不损失精度时才有意义。这也是第一段所说的,第一个要点相当混乱。并且显然有相当多的数字对于这个属性是正确的(任何大于 2^32 但小于 2^64 的 2 的幂)。
  • 重要的检查是这个。如果取原来的 64 位整数值,将其转换为浮点数,然后将该浮点数转换回整数,并得到原始值,则可以传输浮点数代替整数;你可以确定对方可以恢复原来的整数(因为你刚刚自己测试过)。

标签: c++ floating-point cbor


【解决方案1】:

确实有一些数字是这样的:

2^33 可以完美地表示为浮点数,但显然不能表示为 32 位整数。以下代码应按预期工作:

bool representable_as_float(int64_t value) {
    float repr = value;
    return repr >= -0x1.0p63 && repr < 0x1.0p63 && (int64_t)repr == value;
}

重要的是要注意,虽然我们基本上是在做 (int64_t)(float)value 而不是相反 - 如果转换为 float 失去任何精度,我们会感兴趣。

检查 repr 是否小于 int64_t 的最大值很重要,因为否则我们可以调用未定义的行为,因为强制转换为 float 可能会舍入到下一个更高的数字(然后可能大于最大值可能在 int64_t 中)。 (感谢@tmyklebu 指出这一点)。

两个样本:

// powers of 2 can easily be represented
assert(representable_as_float(((int64_t)1) << 33));
// Other numbers not so much:
assert(!representable_as_float(std::numeric_limits<int64_t>::max())); 

【讨论】:

  • 你不是通过(int64_t)(float)0x7fffffffffffffffLL 来获得UB吗?这里对float的转换向上取整,所以对int64_t的转换会溢出,也就是UB。
  • @tmyklebu 公平点。从 int -> float 的转换总是安全的(afaics?),但另一个不是。这使整个事情变得更加有趣。
  • 嗯...所以,这会导致 UB,是吗?
  • 我认为你需要在转换为int64_t之前检查是否repr &gt;= -0x1.0p63 &amp;&amp; repr &lt; 0x1.0p63
【解决方案2】:

以下内容基于Julia's method for comparing floats and integers。这不需要访问 80 位 long doubles 或浮点异常,并且应该在任何舍入模式下工作。我相信这应该适用于任何 C float 类型(IEEE754 与否),并且不会导致任何未定义的行为。

更新:从技术上讲,这假定二进制 float 格式,并且 float 指数大小足以表示 264:对于标准 IEEE754 binary32(您在您的问题中引用),但不是二进制16。

#include <stdio.h>
#include <stdint.h>

int cmp_flt_uint64(float x,uint64_t y) {
  return (x == (float)y) && (x != 0x1p64f) && ((uint64_t)x == y);
}

int main() {
  float x = 0x1p64f;
  uint64_t y = 0xffffffffffffffff;

  if (cmp_flt_uint64(x,y))
    printf("true\n");
  else 
    printf("false\n");
  ;
}

这里的逻辑如下:

  • 只有当x 是区间 [0,264] 中的非负整数时,第一个等式才成立。
  • 第二个检查x(因此(float)y)不是264:如果是这种情况,那么y不能用float精确表示,并且所以比较是错误的。
  • x 的任何剩余值都可以精确转换为 uint64_t,因此我们进行转换和比较。

【讨论】:

    【解决方案3】:

    不,您需要在长双精度尾数可以容纳 63 位的架构上比较 (long double)x == (long double)y。这是因为一些大的 long long int 在您将它们转换为浮点数时会丢失精度,并与非等效浮点数进行比较,但如果您转换为 long double,它不会在该架构上丢失精度。

    以下程序演示了在 x86 上使用 gcc -std=c99 -mssse3 -mfpmath=sse 编译时的这种行为,因为这些设置使用了足够宽的 long double,但防止在计算中隐式使用更高精度的类型:

    #include <assert.h>
    #include <stdint.h>
    
    const int64_t x = (1ULL<<62) - 1ULL;
    const float y = (float)(1ULL<<62);
    // The mantissa is not wide enough to store
    // 63 bits of precision.
    
    int main(void)
    {
      assert ((float)x == (float)y);
      assert ((long double)x != (long double)y);
    
      return 0;
    }
    

    编辑:如果您没有足够宽的长双打,以下方法可能会起作用:

    feclearexcept(FE_ALL_EXCEPT);
    x == y;
    ftestexcept(FE_INEXACT);
    

    我认为,虽然我可能弄错了,但实现可能会在转换过程中以失去精度的方式舍入 x。

    另一个可行的策略是比较

    extern uint64_t x;
    extern float y;
    const float z = (float)x;
    
    y == z && (uint64_t)z == x;
    

    这应该会捕获由于舍入误差导致的精度损失,但如果转换为 z 舍入,它可能会导致未定义的行为。如果在将 x 转换为 z 时将转换设置为向零舍入,它将起作用。

    【讨论】:

    • 我不知道为什么我被否决了,但也许演示这种行为的代码示例会改变你的想法?
    • 我没有投反对票——我实际上对新版本投了赞成票——但是是的,这个例子让你的回答变得有价值。在没有long double 类型的情况下比较整数和浮点值有一些技巧,可以准确地表示每种原始类型的所有值。 gynvael.coldwind.pl/?id=535twitter.com/spun_off/status/467929922259144704
    • 您在底部的建议检查了ftrunc(x) == x,但x 在您的示例中是一个整数(即使假设xy 的角色颠倒了,我也不确定它是否有效,例如float x = 0x1.0p62int64_t y = 0x3fffffffffffffff)。
    • 哎呀,底部的那个位是另一个问题的答案。
    • @Lorehead 在所谓的“往返策略”中,(uint64_t)z 可能会导致未定义的行为,例如对于x 设置为0xffffffffffffffff 导致z0x1.0p64
    猜你喜欢
    • 2015-06-26
    • 2013-04-18
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-18
    • 1970-01-01
    • 2012-04-19
    相关资源
    最近更新 更多