比较uint64_t和float的数值等价

时间:2015-09-27 17:11:35

标签: c++ floating-point cbor

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

  • 什么32位浮点数可以大于64位整数,在数值上等于它们?
  • 比较float x; uint64_t y; (float)x == (float)y是否足以确保价值相等?这种比较会是真的吗?

RFC 7049 §3.6. Numbers

  

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

3 个答案:

答案 0 :(得分: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)(浮点)值而不是相反 - 我们感兴趣的是浮动的转换会失去任何精度。

检查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())); 

答案 1 :(得分:1)

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

更新:从技术上讲,它采用二进制float格式,并且float指数大小足以表示2 64 :对于标准IEEE754来说,这当然是正确的binary32(在你的问题中引用),但不是,例如,binary16。

#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,2 64 ]中的非负整数时,第一个相等才可能为真。
  • 第二次检查x(因此(float)y)不是2 64 :如果是这种情况,那么y无法完全由float,因此比较为假。
  • x的任何剩余值都可以完全转换为uint64_t,因此我们进行投射和比较。

答案 2 :(得分:-1)

不,你需要在一个长双尾数的尾数可以容纳63位的架构上比较(long double)x == (long double)y。这是因为当你将它们转换为float时,一些大的long long int会丢失精度,并且比较等于非等价的float,但是如果你转换为long double,它将不会失去该架构的精度。

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

#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时将转换设置为零,则它将起作用。