比较 uint64_t 和 float 的数值等价性

Posted

技术标签:

【中文标题】比较 uint64_t 和 float 的数值等价性【英文标题】:Comparing uint64_t and float for numeric equivalence 【发布时间】: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 位整数值,将其转换为浮点数,然后将该浮点数转换回整数,并得到原始值,则可以传输浮点数代替整数;你可以确定对方可以恢复原来的整数(因为你刚刚自己测试过)。 【参考方案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

以上是关于比较 uint64_t 和 float 的数值等价性的主要内容,如果未能解决你的问题,请参考以下文章

uint8_t / uint16_t / uint32_t /uint64_t 这些数据类型是什么?

uint8_t / uint16_t / uint32_t /uint64_t 这些数据类型是什么?

如何在 ARM 上的 uint64_t 和 poly64_t 之间进行转换?

C语言之如何输出uint32_t和uint64_t和16进制

防止将 uint64_t 转换为 uint16_t

为啥 uint32_t 与 uint64_t 速度不同?