fix(07-03): #06-04-2 round-trip multi-byte and negative bigint parameters

- Encode bigint bytes by bit-shift mask instead of the broken accumulator
- Decode the bigint length prefix and payload bytes unsigned
- Encode negative bigint as dedicated type 8 with magnitude payload
- Un-skip the multi-byte and heterogeneous bigint cases, add focused cases
This commit is contained in:
unanmed 2026-09-15 18:37:21 +08:00
parent 528b8c91a5
commit 965e002476
2 changed files with 46 additions and 19 deletions

View File

@ -274,8 +274,8 @@ describe('ReplayArray param codec', () => {
expect(firstParamToken(array)).toBe(7);
});
// 疑似 bug:bigint 编码循环缺少按字节右移,多字节 bigint 只能还原最低字节,详见 06-TEST-FINDINGS.md #06-04-2,修复后取消 skip
it.skip('round-trips a multi-byte bigint', () => {
// 验证多字节 bigint 各幅值字节经 type 7 完整读回
it('round-trips a multi-byte bigint', () => {
const array = createArray();
const value = 0x0102030405060708n;
array.add(0, [value]);
@ -283,6 +283,36 @@ describe('ReplayArray param codec', () => {
expect(array.get(0).params).toEqual([value]);
});
// 负 bigint 使用独立类型码 8,载荷为幅值 |n|,解码后取负(A9)
it('round-trips negative bigint values through the dedicated type', () => {
const negativeCases: readonly bigint[] = [
-1n,
-128n,
-129n,
-256n,
-65537n
];
for (const value of negativeCases) {
const array = createArray();
array.add(0, [value]);
expectParamTyped(array.get(0).params[0], value);
expect(firstParamToken(array)).toBe(8);
}
});
// 非负 bigint 仍为 type 7,单字节与多字节幅值逐字节不变
it('round-trips non-negative bigint values through type 7', () => {
const nonNegativeCases: readonly bigint[] = [100n, 0x0102030405060708n];
for (const value of nonNegativeCases) {
const array = createArray();
array.add(0, [value]);
expectParamTyped(array.get(0).params[0], value);
expect(firstParamToken(array)).toBe(7);
}
});
// 验证超过 int32 范围的非负 int64 参数经 type 4 精确读回
it('round-trips int64 values above the int32 range', () => {
const array = createArray();
@ -320,8 +350,8 @@ describe('ReplayArray param codec', () => {
}
});
// 疑似 bug:多字节 bigint 与超 int32 的 int64 混在同一步时同样失真,详见 06-TEST-FINDINGS.md #06-04-1/#06-04-2,修复两个编码与解码缺陷后取消 skip
it.skip('round-trips a heterogeneous step mixing a multi-byte bigint and an int64 value', () => {
// 验证多字节 bigint 与超 int32 的 int64 混在同一步时逐参数精确读回
it('round-trips a heterogeneous step mixing a multi-byte bigint and an int64 value', () => {
const array = createArray();
array.add(0, [true, 0x0102030405060708n, 2147483648, 'x']);

View File

@ -259,23 +259,20 @@ export class ReplayArray implements IReplayArray {
byteLength: byte
};
} else if (typeof param === 'bigint') {
// 7 - bigint
// 7 - 非负 bigint / 8 - 负 bigint,均以幅值按字节写入
const wall = 2n ** 2047n;
if (param > wall - 1n || param < -wall) {
logger.warn(152);
}
const bit = param.toString(2);
const magnitude = param < 0n ? -param : param;
const bit = magnitude.toString(2);
const length = Math.ceil(bit.length / 8);
const arr = new Uint8Array(length);
let total = 0n;
for (let i = 0; i < length; i++) {
const base = param - total;
const remain = base % 256n;
total += remain << (BigInt(i) * 8n);
arr[i] = Number(remain);
arr[i] = Number((magnitude >> (8n * BigInt(i))) & 0xffn);
}
return {
paramType: 7,
paramType: param < 0n ? 8 : 7,
paramValue: arr,
byteLength: arr.length + 2
};
@ -380,8 +377,8 @@ export class ReplayArray implements IReplayArray {
} else if (param.paramType === 6) {
// 6 - float
this.paramView.setFloat64(index + 1, num);
} else if (param.paramType === 7) {
// 7 - bigint
} else if (param.paramType === 7 || param.paramType === 8) {
// 7 - 非负 bigint / 8 - 负 bigint,写入幅值字节
this.paramArray[index + 1] = arr.length;
this.paramArray.set(arr, index + 2);
} else if (param.paramType === 9) {
@ -636,16 +633,16 @@ export class ReplayArray implements IReplayArray {
// 6 - float
byte = 9;
value = this.paramView.getFloat64(startIndex + 1);
} else if (type === 7) {
// 7 - bigint
const length = this.paramView.getInt8(startIndex + 1);
} else if (type === 7 || type === 8) {
// 7 - 非负 bigint / 8 - 负 bigint,读回幅值后按类型码还原符号
const length = this.paramView.getUint8(startIndex + 1);
let base = 0n;
for (let i = 0; i < length; i++) {
const num = this.paramView.getInt8(startIndex + 2 + i);
const num = this.paramView.getUint8(startIndex + 2 + i);
base += BigInt(num) << (8n * BigInt(i));
}
byte = length + 2;
value = base;
value = type === 8 ? -base : base;
} else if (type === 9) {
// 9 - string
const length = this.paramView.getInt32(startIndex + 1);