protobuf.js 7.1.2中fixed64字段编解码数值不匹配问题咨询
关于protobuf.js fixed64编解码数值不匹配的问题
问题描述
使用protobuf.js 7.1.2版本时,定义了一个包含单个fixed64字段的Proto消息,部分数值在编解码后出现数值不匹配的情况。相关代码及现象如下:
执行步骤
$ npm i $ node index.js
代码文件
awesome.proto
syntax = "proto3"; package awesomepackage; message AwesomeMessage { fixed64 awesome_num = 1; }
index.js
const protobuf = require('protobufjs'); protobuf.load("awesome.proto", function (err, root) { const AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage"); const payload = {awesomeNum: 1666189808901000000}; const message = AwesomeMessage.create(payload); console.log(JSON.stringify(message)); // 输出: { awesomeNum: 1666189808901000000 } const buffer = AwesomeMessage.encode(message).finish(); const decodedMessage = AwesomeMessage.decode(buffer); console.log(JSON.stringify(decodedMessage)); // 输出: { awesomeNum: 1666189808900999936 } });
同时,生成的AwesomeMessage#encode代码如下:
(function anonymous(Writer,types,util ) { return function AwesomeMessage$encode(m,w){ if(!w) w=Writer.create() if(m.awesomeNum!=null&&Object.hasOwnProperty.call(m,"awesomeNum")) w.uint32(9).fixed64(m.awesomeNum) return w } })
疑问:
- 为什么
awesomeNum会出现不匹配?这是预期情况吗?忽略了什么? - 生成的encode代码里的
uint32是否应该改为uint64?
问题解答
1. 数值不匹配的原因及是否为预期情况
这是预期情况,根源在于JavaScript原生Number类型的精度限制:
- JavaScript的
Number是双精度浮点数,只能精确表示2^53(约9.007×10¹⁵)以内的整数。 - 你使用的数值
1666189808901000000已经远大于2^53,当你把它赋值给payload.awesomeNum时,JavaScript已经无法精确存储这个数值,实际内存中存储的是一个近似值。 - 编码时protobuf.js使用的是这个近似值,解码后自然会得到和原数值有差异的结果。
2. 解决方法
要保持64位整数的精度,需要使用protobuf.js提供的Long类型来处理:
修改index.js中的payload定义:
const payload = {awesomeNum: protobuf.Long.fromValue(1666189808901000000)};
此时编解码后,decodedMessage.awesomeNum会是一个Long对象,调用其toString()或toNumber()(如果数值在2^53以内)就能得到精确值。
3. 关于encode代码中的uint32(9)
不需要改成uint64,这是符合Protobuf编码规则的:
- 这里的
uint32(9)是字段标签与wire类型的组合编码,不是字段值的类型。 - Protobuf的wire类型中,
fixed64对应的wire type是1;字段编号为1时,组合计算方式是(字段编号 << 3) | wire type,即(1 << 3) | 1 = 9。 - 这个组合值用varint编码(这里数值9刚好可以用一个uint32表示),所以调用
uint32(9)是完全正确的。
内容的提问来源于stack exchange,提问作者luckslovez
相关产品推荐
相关产品推荐

