如何以最高精度将 uint64_t 转换为 0 到 1 之间的双精度/浮点型 (C++)?

问题描述 投票:0回答:3

我正在编写一个基于无符号整数的图像类。我当前使用 uint8_t 和 uint16_t 缓冲区来处理 8 位和 16 位 RGBA 像素,要从 16 位转换为 8 位,我只需获取 16 位值,除以 std::numeric_limits< uint16_t >:: max() 转换为 double,然后乘以 255。

但是,如果我想要一个每个 RGBA 分量都有 64 位无符号整数的图像(我知道,它高得离谱),我该如何找到 0 和 1 之间的浮点数/双精度数来表示 0 和 1 之间的距离我的像素值的最大 uint64_t 是?我认为转换为双精度数不起作用,因为双精度数通常是 64 位浮点数,并且您无法捕获 64 位浮点数中的所有 64 位无符号整数值。不转换为浮点数/双精度数的除法只会给我 0 或有时 1。

查找 0 到 1 之间的浮点值(代表 0 与无符号 64 位值的最大可能值之间的距离)的最准确方法是什么?

c++ floating-point 64-bit
3个回答
6
投票

查找 0 到 1 之间的浮点值(代表 0 与无符号 64 位值的最大可能值之间的距离)的最准确方法是什么?

可以直接将 [0...264) 范围内的整数值映射到 [0 ... 1.0)。

  1. uint64_t
    转换为
    double

  2. 缩放 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。

双映射(uint64_t u){ 双 y = (双) u; y /= Two64f; 如果(y >= 1.0){ y = nextafter(y, 0.0); } }


[2024 年 12 月]

替代方案:

向下舍入

一种稍微不太准确的方法,但更一致:

double map_round_down(uint64_t u) {
  int save_round = fegetround();
  fesetround(FE_TOWARDZERO /* or FE_DOWNWARD */);  // Maybe add error checking?
  double y = (double) u; 
  y /= Two64f;
  fesetround(save_round);
  return y;
}

2
投票

您可以从以下 Java 的 java.util.Random

nextDouble()
方法的代码开始。它需要 53 位并由它们形成一个双精度数:

   return (((long)next(26) << 27) + next(27))
     / (double)(1L << 53);

我将使用 long 的最高有效 26 位作为移位值,并使用接下来的 27 位来填充低位。这会丢弃输入的最低有效 64-53 = 11 位。

如果区分非常小的值特别重要,您还可以使用次正规数,

nextDouble()
不会返回。


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;
}
© www.soinside.com 2019 - 2024. All rights reserved.