工作遇到的问题,记录一下。正常情况下,这些都是很basic的,但有些祖传代码过于久远,过去没触发的问题,在当下的使用情形下就会出问题。所以,不是先人的锅,也不是后人的锅:)。

我们服务器的代码是 C++ & lua (lua 版本是 5.4.6),会用 64 位大整数(其实只用到63位)来构造用户的 ID,其中高 17 位是区域 ID(AID),低46位是自增 ID,即 UID = AID << 46 + INC_ID

C++ 跟 lua 的接口那里,使用了 lua_pushnumber,而 lua_pushnumber 是把数字当成 double 处理的,虽然 double 能表示的数字范围很大,但其精度是有限的,当它作为整数时,可以精确表示的整数范围是 -(2^53-1)2^53-1

这里需要说一下的 double 内部,按照 IEEE 754 标准:1 个符号位,11 个指数位,52 个尾数位。那么为何 52 位的尾数,可以达到 53 位的有效精度?这是 double 规范化数的一个设计,作为规范化数时,它的最高位有个隐藏的 1,假设 52 位尾数是 F,那么实际是 1.F 而不是 0.F。所以,当尾数全为 1 时,最终会有 53 个 1。

关于 IEEE 754、double 规范化数相关的要点,可自行 google 或 chatgpt。总之,可以百分百确定的是,double 作为整数时,它的有效表示精度是 53 位。

搞清楚了 double 的精度之后,就可以知道,当我们的 AID 大于等于 128 的时候,构造出来的 UID 就无法被 double 精确表示了。128 << 46 = 9007199254740992,而 2^53-1 = 9007199254740991

超过 2^53-1 之后,double 也不是每个数都不精确,它会四舍五入,结果就是在 C++ 层不同的数,到了 lua 层变成了相同的数,比如这些数:

9077567999022803,
9077567999022805,
9077567999022799,
9077567999022793,
14073748835534081,
14073748835533033

所以,当下的解决方案就是对于整数,使用 lua_pushinteger 来往 lua 里 push 数字。


但作为 ID,其实用 string 来存储和传输更合理一些,可适应各种变化。这里也不必纠结什么性能问题。