MikeMcl / bignumber.js

A JavaScript library for arbitrary-precision decimal and non-decimal arithmetic
http://mikemcl.github.io/bignumber.js
MIT License
6.64k stars 741 forks source link

Rehydration after structural cloning #317

Open florianbepunkt opened 2 years ago

florianbepunkt commented 2 years ago

Structural Cloning a BigNumber instance removes all prototype methods and leaves a plain object, e. g.

const plainObj = { s: 1, e: -2, c: [ 50 ] }

I thought that it would be possible to rehydrate/restore a BigNumber obj like const bn = new BigNumber(plainObj), however the resulting value is alway NaN.

TDLR

import assert from 'assert';
import BigNumber from 'bignumber.js';

const obj = { c: [50], e: 1, s: 1 };
const rehydrated = new BigNumber(obj);

assert.strictEqual(BigNumber.isBigNumber(rehydrated), true);
assert.strictEqual(rehydrated.isNaN(), false);

Why is this? I thought the object would contain all "state" that is needed to recreate a BigNumber. Is there a way to achieve what I would like to do without converting the value to a string / number first?

EDIT The cause of this issue is that (a) BigNumber.js accepts an object literal, but you need to provide the property _isBigNumber: true to this literal (b) Structured cloning removes _isBigNumber: true from the object

import BigNumber from 'bignumber.js';
import { deserialize, serialize } from 'v8';

const obj = { c: [50], e: 1, s: 1, _isBigNumber: true };
const rehydrated = new BigNumber(obj); // working
const clone = deserialize(serialize(rehydrated)); // structure cloning node.js
const rehydratedClone = new BigNumber(clone); // not working, since clone is missing _isBigNumber: true

Could we skip the _isBigNumber: true check in the constructor? If someone provides an object that adheres to BigNumber's data structure, couldn't we safely assume that this is a BigNumber? This would add the benefit, that BigNumber objects would be serializable via StructuredClone as well as JSON.

Possible solution

https://github.com/MikeMcl/bignumber.js/blob/690d9964fedeb9762e84ae233206292a23ea730a/bignumber.js#L191

Changing this to if (v && typeof v.c !== "undefined" && typeof v.s !== "undefined" && typeof v.e !== "undefined") { would solve the issue, while all tests still pass. So, basically an obj { s: 1, e: -2, c: [ 50 ] } is not a BigNumber, hence BigNumber.isBigNumber(obj) is false, but you can construct a BigNumber from it: new BigNumber(obj).

Then it only becomes a habit of coding to re-encode your BigNumber values before you use them, e. g.

const someFn = (valueThatMightBeSerializedOrNot: BigNumber) => new BigNumber(valueThatMightBeSerializedOrNot).plus(1);