双精度装不下纳秒时间戳
以毫秒为单位的当前时间戳约为 1.7 乘 10 的 12 次方,离双精度整数安全上限 2 的 53 次方(约 9.007 乘 10 的 15 次方)还有余量;微秒级已接近,纳秒级约 1.7 乘 10 的 18 次方则明确越界。一旦某个环节把时间戳当浮点数或按数字解析,末几位会被静默改写,做唯一键、排序或差值计算时就出现难以复现的错乱。
基本信息
| 双精度安全整数 | 2 的 53 次方,约 9007199254740992,是 IEEE 754 双精度可精确表示的连续整数上限 |
|---|---|
| 毫秒量级 | 当前约 1.7 乘 10 的 12 次方,安全;可一直用到位数增长约三个数量级 |
| 微秒量级 | 当前约 1.7 乘 10 的 15 次方,仍在安全范围内但余量很小 |
| 纳秒量级 | 当前约 1.7 乘 10 的 18 次方,超出安全范围,末位约百纳秒级误差 |
| 64 位整型 | long 与 int64 可完整表示纳秒时间戳,问题只出在浮点与 JS 数字 |
典型使用场景
- 前后端接口传纳秒时间戳,前端解析后末位变化,用于幂等的键对不上
- 日志聚合按时间戳排序,精度丢失导致同毫秒事件顺序随机
- 消息去重与顺序控制:时间戳作为唯一标识时精度必须无损
- 数据库字段选型:毫秒以上精度需要 64 位或有小数秒精度的时间类型
- 性能测量:统计微秒级耗时用整数纳秒而非浮点秒
精度丢失演示与安全承载
精度丢失演示与安全承载
// 越界数字在解析时就被改写
JSON.parse('{"ns":1700000000000000001}').ns // 1700000000000000000
Number('1700000000000000001') // 1700000000000000000
// 安全做法一:以字符串承载大整数时间戳
JSON.parse('{"ns":"1700000000000000001"}').ns // 字符串原样保留
// 安全做法二:拆成秒与纳秒两个字段(如 Protobuf Timestamp 思路)
// {"seconds":1700000000,"nanos":1}
// 安全做法三:前端用 BigInt 处理字符串
BigInt('1700000000000000001') + 1n // 1700000000000000002n承载方式对照
| 单位 | 当前量级位数 | 安全承载方式 | 风险点 |
|---|---|---|---|
| 秒 | 10 位 | 整数或字符串均可 | 32 位整型会在 2038 年溢出 |
| 毫秒 | 13 位 | 整数或字符串,数据库用 64 位 | 32 位整型远不够,会截断 |
| 微秒 | 16 位 | 字符串或 64 位整型 | 浮点余量小,转换中易舍入 |
| 纳秒 | 19 位 | 字符串、BigInt 或整型字段对 | JS 数字与浮点运算必然丢末位 |
| 混合精度 | 不定 | 统一到最高精度再存储 | 同列混单位导致排序错乱 |
数据库层面的坑同样常见:32 位整型无法承载毫秒时间戳;不带小数秒精度的时间类型会把毫秒截断为 0。需要毫秒或微秒时显式声明小数秒位数(例如时间类型后缀 3 或 6),具体上限以官方文档为准。
需要无损传递纳秒时间戳时,优先选择「秒 + 纳秒」两个整型字段的表示法,或直接以十进制字符串承载;这比依赖某个语言的数值类型更稳妥。