MikeMcl / bignumber.js

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

Allow creating BigNumber with {s, e, c} #220

Closed iamdoron closed 5 years ago

iamdoron commented 5 years ago

Hi,

BigNumber is not yet stable (and might change forever), but I think we can define BigNumberData which is pretty stable:

  export interface BigNumberData {
    /**
     * The coefficient of the value of this BigNumber, an array of base 1e14 integer numbers.
     */
    readonly c: number[];

     /**
     * The exponent of the value of this BigNumber, an integer number, -1000000000 to 1000000000.
     */
    readonly e: number;

     /**
     * The sign of the value of this BigNumber, -1 or 1.
     */
    readonly s: number;
  }

That will actually allow multiple packages to have different BigNumber versions, but still interact safely without converting to string. Nice thing about this way is that if you have BigNumber7 & BigNumber8,

new BigNumber8(new BigNumber7('123'))
new BigNumber7(new BigNumber8('123'))

just works (presumably safely, even with breaking changes in BigNumber v8 that didn't change BigNumberData).

Maybe we can also version this BigNumberData if you believe it will change in the future. I didn't do it in this PR , but for example:

  export interface BigNumberData {
    /**
     * The coefficient of the value of this BigNumber, an array of base 1e14 integer numbers.
     */
    readonly c: number[];

     /**
     * The exponent of the value of this BigNumber, an integer number, -1000000000 to 1000000000.
     */
    readonly e: number;

     /**
     * The sign of the value of this BigNumber, -1 or 1.
     */
    readonly s: number;
     /**
     * The version of this BigNumber representation 
     */
    readonly ver: number
  }

and once ver is changed BigNumber will throw when trying to construct with unknown versions

MikeMcl commented 5 years ago

Hi, I'll have a look at this properly later this week.

jcramer commented 5 years ago

Funny, I was coming here to create a similar request.

We are storing our application state as documents to mongoDB and we have a lot of use of BigNumber. When the documents are saved to Mongo bignumber objects are recorded as { s: number, e: number, c: [number], "_isBigNumber": bool } within the database.

Having a BN constructor that allows creation from this would be hugely helpful to us when restoring our state from the db documents.

i.e., we need something like: new BigNumber({ s: number, e: number, c: [number], "_isBigNumber": bool })

MikeMcl commented 5 years ago

You have defined the BigNumberData interface within the BigNumber namespace, so

BigNumber.BigNumberData

would be added to the existing exports:

//   class      BigNumber  (default export)
//   type       BigNumber.Constructor
//   type       BigNumber.Instance
//   type       BigNumber.ModuloMode
//   type       BigNumber.RoundingMOde
//   type       BigNumber.Value
//   interface  BigNumber.Config
//   interface  BigNumber.Format

I would prefer

export interface Object {

so the export would be

BigNumber.Object

whicih seems more in keeping.

The alternative is to move the interface out of the BigNumber namespace, but, on balance, I'd prefer to keep all the exported symbols within it.

I don't want any version property.

@jcramer

I'm not sure about requiring the "_isBigNumber" property, as in bignumber.mjs (the ES module version of bignumber.js) it doesn't exist (a Symbol is used instead).

iamdoron commented 5 years ago

@MikeMcl

MikeMcl commented 5 years ago

There is also BigNumber Infinity which is {c: null, e: null, s: 1}.

My preference is:

interface Object {
  readonly c: number[] | null;
  readonly e: number | null;
  readonly s: number | null;
}

(--strictNullChecks recommended.)

On balance, I prefer not to have the [key:string]: any;, although I can see its appeal in terms of flexibility, so it is a very close call.

No need to export BigNumber.Object. BigNumber.Value is enough.

export type Value = string | number | BigNumber | Object;
MikeMcl commented 5 years ago

Thanks, Doron.

v8.1.1 released.

I went with the following in the end:

interface Instance {
  readonly c: number[] | null;
  readonly e: number | null;
  readonly s: number | null;
  [key: string]: any;
}

type Value = string | number | Instance;

To create a BigNumber from an object:

const n = { s: 1, e: 2, c: [ 777, 12300000000000 ], _isBigNumber: true };
const x = new BigNumber(n);

To check that a BigNumber instance is well-formed:

BigNumber.DEBUG = true;
BigNumber.isBigNumber(n);    // true
BigNumber.isBigNumber(x);    // true

See isBigNumber.