1

通信对等方发送了一个 uint64_t 数据字段,它带有我需要存储到不支持无符号整数类型的 Postgresql-11 数据库中的订单 ID。虽然实际数据可能超过 2^63,但我认为INT8Postgresql11 中的一个文件可以容纳它,如果我仔细进行一些转换。

假设有:

uint64_t order_id = 123; // received
int64_t  to_db;          // to be writed into db

我计划使用以下方法之一将 uint64_t 值转换为 int64_t 值:

  1. to_db = order_id; // 直接赋值;
  2. to_db = (int64_t)order_id; //c 风格的强制转换;
  3. to_db = static_cast<int64_t>(order_id);
  4. to_db = *reinterpret_cast<const int64_t*>( &order_id );

当我需要从数据库加载它时,我可以进行反向转换。

我知道它们都可以工作,我只是对哪个最符合 C++ 标准感兴趣。

换句话说,哪种方法在任何编译器的任何 64 位平台上始终有效?

谢谢!!!

4

4 回答 4

1

取决于它将在哪里编译和运行......没有 C++20 支持的任何不能完全移植的。

没有那个的最安全的方法是通过改变值的范围来自己进行转换,比如

int64_t to_db = (order_id > (uint64_t)LLONG_MAX) 
           ? int64_t(order_id - (uint64_t)LLONG_MAX - 1) 
           : int64_t(order_id ) - LLONG_MIN;

uint64_t from_db = (to_db < 0) 
                    ? to_db + LLONG_MIN
                    : uint64_t(to_db) +  (uint64_t)LLONG_MAX  + 1;

如果order_id大于 (2^63 -1),则order_id - (uint64_t)LLONG_MAX - 1产生非负值。如果不是,则强制转换为有符号是明确定义的,并且减法可确保将值转移到负范围内。

在反向转换期间,to_db + LLONG_MIN将值放入 [0, ULLONG_MAX] 范围内。

并在阅读时做相反的事情。您使用的数据库平台或编译器在将无符号值的二进制表示转换为有符号时可能会做一些糟糕的事情,更不用说确实存在不同的有符号格式。

出于同样的原因,跨平台协议通常涉及使用字符串格式或“最小位值”来将浮点值表示为整数,即编码定点。

于 2020-12-02T09:52:50.410 回答
1

我会去memcpy。它避免了(?见注释)未定义的行为,并且通常编译器优化任何字节复制:

int64_t uint64_t_to_int64_t(uint64_t u)
{
  int64_t i;
  memcpy(&i, &u, sizeof(int64_t));
  return i;
}

order_id = uint64_t_to_int64_t(to_db);

GCC 为-O2生成了最佳程序集uint64_t_to_int64_t

mov rax, rdi
ret

现场演示:https ://godbolt.org/z/Gbvhzh

于 2020-12-02T10:20:05.200 回答
0

此功能似乎无 UB

int64_t fromUnsignedTwosComplement(uint64_t u)
{
    if (u <= std::numeric_limits<int64_t>::max()) return static_cast<int64_t>(u);
    else return -static_cast<int64_t>(-u);
}

它减少到优化下的无操作。

另一个方向的转换是直接转换为uint64_t. 它总是定义明确的。

于 2020-12-02T10:14:43.343 回答
0

只要值在范围内,所有四种方法都将始终有效。第一个将在许多编译器上生成警告,因此可能不应该使用。第二个更像是 C 习语而不是 C++ 习语,但在 C++ 中被广泛使用。最后一个是丑陋的,依赖于标准中的细微细节,不应该使用。

于 2020-12-02T09:39:59.887 回答