3

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

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

RFC 7049 §3.6。数字

出于本规范的目的,相同数值的所有数字表示都是等效的。这意味着编码器可以将浮点值 0.0 编码为整数 0。然而,这也意味着,如果编码器认为这些值是可取的,那么期望找到整数值的应用程序可能会找到浮点值,例如就像浮点值比 64 位整数更紧凑一样。

4

3 回答 3

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())); 
于 2015-09-27T17:23:30.017 回答
1

以下是基于Julia 比较浮点数和整数的方法。这不需要访问 80 位long double或浮点异常,并且应该在任何舍入模式下工作。我相信这应该适用于任何 Cfloat类型(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");
  ;
}

这里的逻辑如下:

  • 只有当 是区间 [0,2 64 ]x中的非负整数时,第一个等式才能为真。
  • 第二个检查x(因此(float)y)不是 2 64:如果是这种情况,则y不能用 a 精确表示float,因此比较是错误的。
  • 的任何剩余值x都可以精确地转换为 a uint64_t,因此我们进行转换和比较。
于 2015-09-28T09:22:28.387 回答
-1

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

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

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

于 2015-09-27T18:13:46.310 回答