Skip to content

Commit 72200c8

Browse files
committed
fix(core/cbor): handle exponent notation when serializing NumericValue containers
1 parent f8e5c6b commit 72200c8

5 files changed

Lines changed: 74 additions & 6 deletions

File tree

.changeset/fuzzy-dragons-fix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/core": patch
3+
---
4+
5+
fix: handle exponent notation when serializing NumericValue wrappers

packages/core/src/submodules/cbor/cbor-encode.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,19 @@ export function encode(_input: any): void {
179179
continue;
180180
} else if (typeof input === "object") {
181181
if (input instanceof NumericValue) {
182-
const decimalIndex = input.string.indexOf(".");
183-
const exponent = decimalIndex === -1 ? 0 : decimalIndex - input.string.length + 1;
184-
const mantissa = BigInt(input.string.replace(".", ""));
182+
let str = input.string;
183+
let expOffset = 0;
184+
185+
const eIndex = str.search(/[eE]/);
186+
if (eIndex !== -1) {
187+
expOffset = Number(str.slice(eIndex + 1));
188+
str = str.slice(0, eIndex);
189+
}
190+
191+
const decimalIndex = str.indexOf(".");
192+
const fractionDigits = decimalIndex === -1 ? 0 : str.length - decimalIndex - 1;
193+
const exponent = expOffset - fractionDigits;
194+
const mantissa = BigInt(str.replace(".", ""));
185195

186196
data[cursor++] = 0b110_00100; // major 6, tag 4.
187197
encodeInteger(majorList, 2);

packages/core/src/submodules/cbor/cbor.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,22 @@ describe("cbor", () => {
322322
]);
323323
});
324324

325+
it("should round-trip NumericValue with exponent notation", () => {
326+
for (const bigDecimal of ["1.5e10", "3E-20", "-2.0e+5", "100E3", "1e2", ".5e3"]) {
327+
const numericValue = new NumericValue(bigDecimal, "bigDecimal");
328+
const serialized = cbor.serialize(numericValue);
329+
330+
const major = serialized[0] >> 5;
331+
expect(major).toEqual(0b110); // 6
332+
333+
const tag = serialized[0] & 0b11111;
334+
expect(tag).toEqual(0b0100); // 4
335+
336+
const deserialized = cbor.deserialize(serialized);
337+
expect(deserialized).toBeInstanceOf(NumericValue);
338+
}
339+
});
340+
325341
it("should round-trip sequences of big numbers", () => {
326342
const sequence = {
327343
map: {

packages/core/src/submodules/cbor/codec-v2/CborShapeSerializer2.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -701,9 +701,19 @@ function writeTag(tagValue: number | bigint, innerValue: unknown): void {
701701
}
702702

703703
function writeNumericValue(nv: NumericValue): void {
704-
const decimalIndex = nv.string.indexOf(".");
705-
const exponent = decimalIndex === -1 ? 0 : decimalIndex - nv.string.length + 1;
706-
const mantissa = BigInt(nv.string.replace(".", ""));
704+
let str = nv.string;
705+
let expOffset = 0;
706+
707+
const eIndex = str.search(/[eE]/);
708+
if (eIndex !== -1) {
709+
expOffset = Number(str.slice(eIndex + 1));
710+
str = str.slice(0, eIndex);
711+
}
712+
713+
const decimalIndex = str.indexOf(".");
714+
const fractionDigits = decimalIndex === -1 ? 0 : str.length - decimalIndex - 1;
715+
const exponent = expOffset - fractionDigits;
716+
const mantissa = BigInt(str.replace(".", ""));
707717

708718
ensure(9);
709719
buf[cursor++] = 0b110_00100; // major 6, tag 4

packages/core/src/submodules/cbor/codec-v2/SinglePassCbor.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,33 @@ describe("CborShapeSerializer2", () => {
188188
expect(cbor.deserialize(singleBytes)).toEqual(cbor.deserialize(multiBytes));
189189
});
190190

191+
it("serializes NumericValue with exponent notation", () => {
192+
const schema = [
193+
3,
194+
"ns",
195+
"Measurement",
196+
0,
197+
["value"],
198+
[19 satisfies BigDecimalSchema],
199+
] satisfies StaticStructureSchema;
200+
201+
const cases = ["1.5e10", "3E-20", "-2.0e+5", "100E3", "1e2", ".5e3"];
202+
for (const str of cases) {
203+
const data = { value: nv(str) };
204+
205+
multiPass.write(schema, data);
206+
const multiBytes = multiPass.flush();
207+
208+
singlePass.write(schema, data);
209+
const singleBytes = singlePass.flush();
210+
211+
const multiResult = cbor.deserialize(multiBytes);
212+
const singleResult = cbor.deserialize(singleBytes);
213+
expect(singleResult).toEqual(multiResult);
214+
expect(singleResult.value.string).toEqual(multiResult.value.string);
215+
}
216+
});
217+
191218
it("serializes unions with $unknown", () => {
192219
const unionSchema = [4, "ns", "Union", 0, ["a", "b"], [0, 0]] satisfies StaticUnionSchema;
193220
const data = { $unknown: ["c", "hello"] };

0 commit comments

Comments
 (0)