比较uint64_t和float的数值等价性
Comparing uint64_t and float for numeric equivalence
我正在编写一个协议,它使用RFC 7049作为其二进制表示。该标准规定,如果数字的数值与相应的64位数字相等,则协议可以使用32位浮点数表示数字。转换不能导致精度的损失。
- 哪些32位浮点数可以大于64位整数,并且与它们在数字上相等?
- 比较
float x; uint64_t y; (float)x == (float)y
是否足以确保值是相等的?这种比较会正确吗?
RFC 7049§3.6。数字
在本规范中,所有数字表示对于相同的数值是相等的。这意味着编码器可以将0.0的浮点值编码为整数0。然而,它也意味着应用程序期望找到整数值只能找到浮点值,如果编码器决定这些是需要的,例如当浮点值是比64位整数更紧凑。
当然有一些数字是这样的:
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)值,而不是反过来——我们感兴趣的是转换为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()));
下面是基于Julia比较浮点数和整数的方法。这不需要访问80位long double
或浮点异常,并且应该在任何舍入模式下工作。我相信这应该适用于任何C float
类型(IEEE754与否),并且不会导致任何未定义的行为。
更新:从技术上讲,这假设了二进制float
格式,并且float
指数大小足以表示264:对于标准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("truen");
else
printf("falsen");
;
}
这里的逻辑如下:
- 当
x
是区间[0,264]内的非负整数时,第一个等式为真。 - 第二个检查
x
(因此(float)y
)不是264:如果是这种情况,则y
不能由float
精确表示,因此比较为假。 -
x
的任何剩余值都可以准确地转换为uint64_t
,因此我们可以进行强制转换和比较。
不,您需要在长双精度体尾数可以容纳63位的体系结构上比较(long double)x == (long double)y
。这是因为当将一些大的long long int型转换为float型并与不相等的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;
}
Edit:如果你没有足够宽的长双精度,下面的可能工作:
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时将转换设置为向零四舍五入,则可以工作。
- System.InvalidCastException - SQL to C++ - safe_cast<float>
- 没有从阵列<float>到阵列<int>的可行转换
- 数组下标的类型"float*[float]"无效
- 没有合适的构造函数可以从"float"转换为"_D3DCOLORVALUE"
- 为什么我会收到此错误?无法将 {lb, ub} 从<大括号括起来的初始值设定项列表>转换为 float(**)(float*, int)
- 将 **float array 从 C++ Dll 传递给 python
- 错误:二进制'operator*' 'float'和'float[0]'类型的操作数无效
- float* 已在 Gameobject.obj 中定义
- 如何将 qml 的文本转换为 float 和 int
- 为什么将 1 添加到 numeric_limits<float>::min() 返回 1?
- 有没有比static_cast更优雅的从int到float的演员阵容<float>?
- 编译器给出错误:format 指定类型 'float *',但参数的类型'double' [-Wformat]
- 关于uint64的atomic_fetch_add的奇怪行为
- OPENCL 警告:不兼容的指针类型将'float __global[16]'传递给类型为 '__global float4 的参数 *
- 错误:无法将"float*"转换为"float"
- 如何在函数中将字符串和分数存储为(Int 或 float)
- 缩小从double到float的转换
- 着色器将uint8投射到float,并将其重新解释回uint
- 我一直收到错误"cannot convert 'float*' to 'float' in return"
- 将float转换为uint64和uint32的行为很奇怪