C++ 将32位无符号整数精确转换为范围(-1;1)中的浮点
根据,一半的浮点数在区间[-1,1]内。您能否建议如何利用这一事实来取代将32位无符号整数转换为浮点数的天真转换(同时保持均匀分布) 朴素的代码:C++ 将32位无符号整数精确转换为范围(-1;1)中的浮点,c++,c,algorithm,cuda,floating-point,C++,C,Algorithm,Cuda,Floating Point,根据,一半的浮点数在区间[-1,1]内。您能否建议如何利用这一事实来取代将32位无符号整数转换为浮点数的天真转换(同时保持均匀分布) 朴素的代码: uint32_t i = /* randomly generated */; float f = (float)i / (1ui32<<31) - 1.0f; uint32_t i=/*随机生成*; float f=(float)i/(1ui32您可以使用double进行计算,这样您就不会丢失uint32\u t值的任何精度,然后将结果
uint32_t i = /* randomly generated */;
float f = (float)i / (1ui32<<31) - 1.0f;
uint32_t i=/*随机生成*;
float f=(float)i/(1ui32您可以使用double
进行计算,这样您就不会丢失uint32\u t
值的任何精度,然后将结果分配给float
float f = (double)i / (1ui32<<31) - 1.0;
float f=(double)i/(1ui32我建议(如果您希望避免除法并使用1.0*2^-32的可精确表示的浮点起始值):
如果您放弃均匀分布约束,则仅在32位整数算术上可行:
//---------------------------------------------------------------------------
浮点i32到f32(整数x)
{
国际贸易;
并集_f32//半结果
{
浮点f;//32位浮点
DWORD u;//32位uint
}y;
//边缘案例
如果(x==0x00000000)返回0.0f;
如果(x<-0x1FFFFFF)返回-1.0f;
如果(x>+0x1ffffff)返回+1.0f;
//转化
y、 u=0;//重置位
if(x>23)&63)-64;//高位6位->指数-1,…-64(非7位以避免非规范化数字)
y、 u |=(exp+127)23)&255;exp-=127;//指数偏差和位位置
如果(exp
有没有办法在不使用双精度浮点的情况下实现这一点
在不对float
的内部进行过多假设的情况下:
移位u
直到设置了最高有效位,将浮点值减半
“保持均匀分布”
50%的uint32\u t
值将在[0.5…1.0]中
25%的uint32\u t
值将在[0.25…0.5]中
12.5%的uint32\u t
值将在[0.125…0.25]中
6.25%的uint32\u t
值将位于[0.0625…0.125)
各种优化都是可能的,特别是假设属性为float
。但是对于最初的答案,我将坚持一种通用的方法
为了提高效率,循环只需从32向下迭代到FLT\u MANT\u DIG
,通常为24
float ui32to0to1(uint32_t u) {
float band = 1.0f/(1llu<<32);
for (int i = 32; (i>FLT_MANT_DIG && ((u & 0x80000000) == 0)); i--) {
u <<= 1;
band *= 0.5f;
}
return (float)u * band;
}
要实现的是,虽然(float)i
确实会损失8位精度(因此它有24位精度),但结果也只有24位精度。因此,这种精度损失不一定是坏事(这实际上更复杂,因为如果i
更小,它将丢失不到8位。但事情会很顺利)
因此,我们只需要修正范围,使原来的非负值映射到INT\u MIN..INT\u MAX
此表达式起作用:(float)(int)(值^0x8000000)/0x8000000
下面是它的工作原理:
(int)(值^0x8000000)
部分翻转符号位,因此0x0
映射到int\u MIN
,而0xffffff
映射到int\u MAX
然后是到float
的转换。这是一些舍入发生的地方,我们会失去精度(但这不是问题)
然后只需除以0x8000000
即可进入范围[-1..1]
。由于此除法只调整指数部分,因此此除法不会失去任何精度
因此,只有一个舍入操作,其他操作不会失去精度。这些操作链应该具有相同的效果,如以无限精度计算结果,然后对浮点值进行舍入(此理论舍入与2.步的舍入效果相同)
但是,绝对可以肯定的是,我已经用蛮力检查了所有32位值,验证了这个表达式产生的值是否与(float)((double)value/0x8000000-1.0)相同可能float f=(float)((double)I/(1UI32在CUDA中,double
上的操作速度太慢。很抱歉,我没有意识到我应该在问题中提到这一点。大多数情况下,将I
转换为浮点值时会丢失精度。小的I
值没有足够的精度丢失任何精度。对于足够大的va,可能会丢失一位i
的LUE(也设置了低位)由于四舍五入的原因。double
的性能不受CUDA的限制,而是受您运行的GPU的限制。一些支持CUDA的GPU比任何x86_64 CPU都具有更高的double
吞吐量。如果您对尽可能快的性能感兴趣,我会尝试消除浮点除法运算。我认为这是可能的ble.取32位统一带符号整数。屏蔽低8位。结果是23位尾数和浮点数的符号位,指数计算起来很简单。i*ldexp(1.0,-32)
isldexp(i,-32)
或i*0x1p-32f
。在C中,坚持float
应该是ldexpf
。类似地,1.0
应该是1.0f
。但是,在居中之前转换为float
会丢失精度。OP可能在某种程度上希望这样做,但最好是在整数中减去,然后在整数中相乘float
。但是在进行减法时会出现无符号/有符号问题。我认为这会在转换过程中损失很多精度…因为double没有理由使用结果的非规范化版本,而对float的转换很可能只是切断尾数位…导致大量整数值映射到同一个v但这只是我的猜测,我可能错了。多个整数值映射到同一个值是不可避免的
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
float ui32to0to1(uint32_t u) {
if (u) {
float band = 1.0f/(1llu<<32);
while ((u & 0x80000000) == 0) {
u <<= 1;
band *= 0.5f;
}
return (float)u * band;
}
return 0.0f;
}
int test(uint32_t u) {
volatile float f0 = (float) ((double)u / (1llu<<32));
volatile float f1 = ui32to0to1(u);
if (f0 != f1) {
printf("%8lX %.7e %.7e\n", (unsigned long) u, f0, f1);
return 1;
}
return 0;
}
int main(void) {
for (int i=0; i<100000000; i++) {
test(rand()*65535u ^ rand());
}
return 0;
}
float ui32to0to1(uint32_t u) {
float band = 1.0f/(1llu<<32);
for (int i = 32; (i>FLT_MANT_DIG && ((u & 0x80000000) == 0)); i--) {
u <<= 1;
band *= 0.5f;
}
return (float)u * band;
}
if (u >= 0x80000000) {
return ui32to0to1((u - 0x80000000)*2);
} else
return -ui32to0to1((0x7FFFFFFF - u)*2);
}