根据articles like this,一半的浮点数在间隔[-1,1]中。您能否建议如何利用这一事实,以将32位无符号整数的天真转换替换为浮点数(同时保持均匀分布)?
原始代码:
uint32_t i = /* randomly generated */;
float f = (float)i / (1ui32<<31) - 1.0f;
这里的问题是,首先将数字i
转换为float
,最多损失8位低位精度。只有这样,数字才会缩放到[0; 2)间隔,然后再缩放到[-1; 1)间隔。
如果知道,请提出针对x86_64 CPU或CUDA的C或C ++解决方案。
更新:带有double
的解决方案对于x86_64很好,但是在CUDA中太慢了。对不起,我没想到会有这样的答复。有什么想法可以在不使用双精度浮点的情况下实现这一目标吗?
答案 0 :(得分:2)
您可以改用double
进行计算,这样就不会对uint32_t
值造成任何精度损失,然后将结果分配给float
。
float f = (double)i / (1ui32<<31) - 1.0;
答案 1 :(得分:1)
如果您删除了均匀分布约束,则仅在32位整数算术上可行:
//---------------------------------------------------------------------------
float i32_to_f32(int x)
{
int exp;
union _f32 // semi result
{
float f; // 32bit floating point
DWORD u; // 32 bit uint
} y;
// edge cases
if (x== 0x00000000) return 0.0f;
if (x< -0x1FFFFFFF) return -1.0f;
if (x> +0x1FFFFFFF) return +1.0f;
// conversion
y.u=0; // reset bits
if (x<0){ y.u|=0x80000000; x=-x; } // sign (31 bits left)
exp=((x>>23)&63)-64; // upper 6 bits -> exponent -1,...,-64 (not 7bits to avoid denormalized numbers)
y.u|=(exp+127)<<23; // exponent bias and bit position
y.u|=x&0x007FFFFF; // mantissa
return y.f;
}
//---------------------------------------------------------------------------
int f32_to_i32(float x)
{
int exp,man,i;
union _f32 // semi result
{
float f; // 32bit floating point
DWORD u; // 32 bit uint
} y;
// edge cases
if (x== 0.0f) return 0x00000000;
if (x<=-1.0f) return -0x1FFFFFFF;
if (x>=+1.0f) return +0x1FFFFFFF;
// conversion
y.f=x;
exp=(y.u>>23)&255; exp-=127; // exponent bias and bit position
if (exp<-64) return 0.0f;
man=y.u&0x007FFFFF; // mantissa
i =(exp<<23)&0x1F800000;
i|= man;
if (y.u>=0x80000000) i=-i; // sign
return i;
}
//---------------------------------------------------------------------------
我选择仅使用29位+符号=〜30位整数,以避免非规范化的数字混乱,而我却懒得编码(这会使您达到30位甚至31位,但要慢得多且复杂得多)。
但是分布不是线性的也不是均匀的:
红色中的是范围float
中的<-1,+1>
,蓝色是范围integer
中的<-1FFFFFFF,+1FFFFFFF>
。
另一方面,两次转换都没有舍入...
PS。我认为也许可以通过对6位指数(64个值)使用预先计算的 LUT 对结果进行某种程度的线性化。
答案 2 :(得分:1)
要实现的事情是,(float)i
确实失去了8位精度(因此它具有24位精度),结果也只有24位精度。因此,这种精度损失不一定是一件坏事(实际上更复杂,因为如果i
较小,则损失少于8位。但是事情会顺利进行的。)
因此,我们只需要固定范围,就可以将原来的非负值映射到INT_MIN..INT_MAX
。
此表达式有效:(float)(int)(value^0x80000000)/0x80000000
。
这是它的工作方式:
(int)(value^0x80000000)
部分翻转符号位,因此0x0
被映射到INT_MIN
,而0xffffffff
被映射到INT_MAX
。float
。这是一些取整的地方,我们会失去精度(但这不是问题)。0x80000000
即可进入范围[-1..1]
。由于该除法只是调整指数部分,因此该除法不会损失任何精度。因此,只有一个舍入,其他操作不会失去精度。这些操作链应具有相同的效果,例如以无限精度计算结果,然后对float
进行舍入(此理论舍入与在步骤2进行舍入具有相同的效果)。
但是,绝对可以肯定,我已经用蛮力验证了所有32位值,该表达式产生的值与(float)((double)value/0x80000000-1.0)
相同。
答案 3 :(得分:0)
我建议(如果您想避免除法,并使用精确的浮点数表示的起始值1.0 * 2 ^ -32):
float e = i * ldexp(1.0,-32) - 1.0;
答案 4 :(得分:0)
任何想法如何在不使用双精度浮点的情况下实现这一目标?
无需过多假设float
的内部:
将u
移至最高有效位,将float
转换值减半。
“保持均匀分布”
uint32_t
值的50%将位于[0.5 ... 1.0)
uint32_t
值的25%将位于[0.25 ... 0.5)
uint32_t
值的12.5%将位于[0.125 ... 0.25)
uint32_t
值的6.25%将在[0.0625 ... 0.125)中
...
#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;
}
一些测试代码可以显示与double
等效的功能。
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
的情况下。但是,对于最初的答案,我将坚持使用通用方法。
为了提高效率,循环仅需要从32迭代到FLT_MANT_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;
}
此答案将[0到2 32 -1]映射到[0.0到1.0)
要映射到[0至2 32 -1]至(-1.0至1.0)。它可以形成-0.0。
if (u >= 0x80000000) {
return ui32to0to1((u - 0x80000000)*2);
} else
return -ui32to0to1((0x7FFFFFFF - u)*2);
}