diff --git a/README.md b/README.md index d50b9d46..50d9c2ec 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ extensionCodec | ExtensionCodec | `ExtensinCodec.defaultCodec` maxDepth | number | `100` initialBufferSize | number | `2048` sortKeys | boolean | false +forceFloat32 | boolean | false ### `decode(buffer: ArrayLike | ArrayBuffer, options?: DecodeOptions): unknown` diff --git a/src/Encoder.ts b/src/Encoder.ts index 3a61aa6e..e95b5e0c 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -18,6 +18,7 @@ export class Encoder { readonly maxDepth = DEFAULT_MAX_DEPTH, readonly initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE, readonly sortKeys = false, + readonly forceFloat32 = false, ) {} encode(object: unknown, depth: number): void { @@ -118,8 +119,16 @@ export class Encoder { } } } else { - this.writeU8(0xcb); - this.writeF64(object); + // non-integer numbers + if (this.forceFloat32) { + // float 32 + this.writeU8(0xca); + this.writeF32(object); + } else { + // float 64 + this.writeU8(0xcb); + this.writeF64(object); + } } } @@ -345,9 +354,14 @@ export class Encoder { this.pos += 4; } + writeF32(value: number) { + this.ensureBufferSizeToWrite(4); + this.view.setFloat32(this.pos, value); + this.pos += 4; + } + writeF64(value: number) { this.ensureBufferSizeToWrite(8); - this.view.setFloat64(this.pos, value); this.pos += 8; } diff --git a/src/encode.ts b/src/encode.ts index cb066b51..94af5307 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -7,6 +7,13 @@ export type EncodeOptions = Partial< maxDepth: number; initialBufferSize: number; sortKeys: boolean; + + /** + * If `true`, non-integer numbers are encoded in float32, not in float64 (the default). + * + * Only use it if precisions don't matter. + */ + forceFloat32: boolean; }> >; @@ -19,7 +26,13 @@ const defaultEncodeOptions = {}; * The returned buffer is a slice of a larger `ArrayBuffer`, so you have to use its `#byteOffset` and `#byteLength` in order to convert it to another typed arrays including NodeJS `Buffer`. */ export function encode(value: unknown, options: EncodeOptions = defaultEncodeOptions): Uint8Array { - const encoder = new Encoder(options.extensionCodec, options.maxDepth, options.initialBufferSize, options.sortKeys); + const encoder = new Encoder( + options.extensionCodec, + options.maxDepth, + options.initialBufferSize, + options.sortKeys, + options.forceFloat32, + ); encoder.encode(value, 1); return encoder.getUint8Array(); } diff --git a/test/encode.test.ts b/test/encode.test.ts index f07d63b3..99aee64b 100644 --- a/test/encode.test.ts +++ b/test/encode.test.ts @@ -3,11 +3,28 @@ import { encode, decode } from "@msgpack/msgpack"; describe("encode", () => { context("sortKeys", () => { - it("canonicalize encoded binaries", () => { + it("canonicalizes encoded binaries", () => { assert.deepStrictEqual(encode({ a: 1, b: 2 }, { sortKeys: true }), encode({ b: 2, a: 1 }, { sortKeys: true })); }); }); + context("forceFloat32", () => { + it("encodes numbers in float64 wihout forceFloat32", () => { + assert.deepStrictEqual(encode(3.14), Uint8Array.from([0xcb, 0x40, 0x9, 0x1e, 0xb8, 0x51, 0xeb, 0x85, 0x1f])); + }); + + it("encodes numbers in float32 when forceFloate32=true", () => { + assert.deepStrictEqual(encode(3.14, { forceFloat32: true }), Uint8Array.from([0xca, 0x40, 0x48, 0xf5, 0xc3])); + }); + + it("encodes numbers in float64 with forceFloat32=false", () => { + assert.deepStrictEqual( + encode(3.14, { forceFloat32: false }), + Uint8Array.from([0xcb, 0x40, 0x9, 0x1e, 0xb8, 0x51, 0xeb, 0x85, 0x1f]), + ); + }); + }); + context("ArrayBuffer as buffer", () => { const buffer = encode([1, 2, 3]); const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteLength);