如何在C++中将uint64_t转换为0到1之间的最大精度的double/float?

9
我正在编写一个基于无符号整数的图像类。目前,我使用uint8_t和uint16_t缓冲区来处理8位和16位RGBA像素,并且为了将16位转换为8位,我只需取16位值,除以std::numeric_limits :: max()转换为双精度,然后乘以255。
但是,如果我想要每个RGBA组件都具有64位无符号整数的图像(我知道,这太高了),那么我该如何找到一个介于0和1之间的浮点数/双精度数,表示我的像素值在0和最大uint64_t之间的距离?我假设转换为双精度不起作用,因为双精度通常是64位浮点数,而且在64位浮点数中无法捕获所有64位无符号整数值。不进行浮点数/双精度数转换就进行除法运算只会给我0或者有时候是1。
最准确的方法是什么,可以找到介于0和1之间的浮点数,表示无符号64位值的最大可能值之间的距离?

{btsdaf} - Kerrek SB
2
转换为双精度浮点数,然后除以1.8446744073709552e+19。为什么你担心双精度浮点数不够准确呢? - Mark Ransom
1
{btsdaf} - Eric Postpischil
{btsdaf} - Kerrek SB
2
将其转换为双精度浮点数并进行除法运算,将会获得最大可能的精确度,IEEE双精度浮点数的规则几乎可以保证这一点。无论如何,你想得太多了。从uint64_tdouble的转换会丢失一些位,但这将是你能够接受的最少的位数 - 而且这些位数在某个时候无论如何都会丢失。结果仍然比99.9%的使用情况所需的精确度要高得多。 - Mark Ransom
显示剩余4条评论
3个回答

5
什么是最准确的方法,在0和最大无符号64位值之间找到一个浮点值,表示它在0和最大值之间的距离?
将范围在[0 ... 2^64)的整数值映射到[0 ... 1.0)可以直接完成。
  1. Convert from uint64_t to double.

  2. Scale by 264 @Mark Ransom

     #define TWO63 0x8000000000000000u 
     #define TWO64f (TWO63*2.0)
    
     double map(uint64_t u) {
       double y = (double) u; 
       return y/Two64f;
     }
    

将范围在 [263...264) 到 [0.5 ... 1.0) 的整数值映射为 252 个不同的 double 值。
将范围在 [262...263) 到 [0.25 ... 0.5) 的整数值映射为 252 个不同的 double 值。
将范围在 [261...262) 到 [0.125 ... 0.25) 的整数值映射为 252 个不同的 double 值。
...
将范围在 [252...253) 到 [2-12 ... 2-11) 的整数值映射为 252 个不同的 double 值。
将范围在 [0...252) 到 [2-13 ... 2-12) 的整数值映射为 252 个不同的 double 值。


将范围在 [0...264) 的整数值映射为 [0 ... 1.0] 更加困难。(注意 ]) 的区别。)


[2021年2月] 我看到这个答案需要重新解释上边缘情况。可能返回的值包括 1.0。


所以,基本上将其除以2^64(作为double)即可得到最大值为1.0的结果。 - Top-Master

2
您可以从以下Java代码开始使用java.util.RandomnextDouble()方法。它获取53位并将其形成一个双精度浮点数:
   return (((long)next(26) << 27) + next(27))
     / (double)(1L << 53);

我建议您使用长整型的最高26位作为移位值,接下来的27位填充低位比特。这会丢弃输入的最不重要的11位(64-53=11)。
如果区分非常小的值特别重要,您还可以使用次正规数,但是nextDouble()不会返回这种类型的数值。

C#看起来非常相似:double asDouble(UInt64 v) { return ((Int64)(v >> 11)) / (double)(1L << 53); } - Jesse Chisholm

1

OP要求使用C++,因此这里提供一个示例: (假设编译器知道类型__int64,这通常是Visual Studio的特性。)

double asDouble(unsigned __int64 v)
{
    return ((__int64)(v >> 11)) / (double)(1L << 53);
}

或者,如果您不介意奇怪的转换方式:

double asDouble(unsigned __int64 v)
{
    // the 0x3FF sets the exponent to the 0..1 range.
    unsigned __int64 vv == (v >> 11) | (0x3FFL << 53);
    return *(double*)&vv;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接