Description
Hiya
https://github.com/NoUJoe/JSBD
This is a full implementation of BigDecimal (and not BigDecimal128)
Let me give a rundown of why I made this and then a rundown of the library.
I was working on a personal project, and needed decimal precision, specificlly add and multiply. Due to it being a personal project, I decided to take on the challenge and implement my own functions. I literally called them "MultiplyTwoStrings" and "AddTwoStrings". These functions became staples in my next few personal projects, I improved them over time too. On one project, I'm trying to sort a bottleneck, and finally decide it's time to concede and use a decimal library, because it will obviously be faster than my code. So I download some decimal libraries (I think the 2 most popular, whatever shows at the top of google/npm). I wrote a test to compare performance vs my functions (add and multiply)... Low and behold, to my utter surprise, mine were faster... I remember sitting there just sorta tilting my head thinking "huh...". I can't remember how much faster, it wasn't like ground breaking 300x or anything, but the main point being, there was no point swapping my functions out for a library.
Blah blah blah, jump forward and I've added a "DivideTwoStrings" and a couple of comparison functions. At some point, I have an epiphany, my functions take strings and return strings (if the names didn't give it away), and the string inputs need to be processed every function call. I can remove this processing if I make a class/object/whatever javascript terminology, and save even more time processing wise. Really obvious, I know... But sometimes it takes a year or so of staring at the same code for it to click...
So I decide to finally make a class, JSBI springs to mind, so I have a search and find this proposal. Thought that I'm doing this anyway, so why not implement this proposal?
And here we are, all caught up!
Ok, so, the library. It is essentially an implementation of this: https://github.com/tc39/proposal-decimal/blob/main/bigdecimal-reference.md
There are a few minor differences, I'll explain them first
-
I included a
decimals
instance property, which returns the amount of decimal places the number has.
(1.12345m).decimals === 5
-
roundingOptions
is different from the proposal, this is because there is prescedent for this in Javascript. See https://developer.mozilla.org/en-US/docs/Web/Javascript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingmode
TheroundingOptions
interface is
{ roundingMode, roundingIncrement, maxFractionDigits }
See the MDN link which has all theroundingMode
androundingIncrement
values -
I included a few extra operators. I did this for "fullness", I'd rather present as full of a implementation as possible, things can always be removed easily.
Other than that, it's to the T. I'll paste all methods/properties at the end, I'm not going to explain them all. The only two that need explaining are:
logicalNot2()
and bitwiseNot2()
. These are shortcuts for
!!
and ~~
respectively. With logcialNot()
and bitwiseNot()
being the single versions.
It's thoroughly tested, I have ~8k lines of tests, across 4 files, in the "tests" folder. (and yes, I used 69.6969 A LOT in the rounding tests, a guy's allowed a little fun when testing 9 rounding modes)
Written in pure Javascript, with a complete type definition file.
I think that covers most of it. Honestly, if anyone is interested/wants to know more, you should just check out the repo. The test files are organised extremely neatly for the purpose of showcasing. The repo is set up as a node module so you should be able to:
npm i "https://github.com/NoUJoe/JSBD"
It's ESM only right now, I've done no transpiling or the likes. It's a very basic setup.
And to wrap up. I have 0 intention of releasing this myself as a package on npm. That's why I'm posting here. It's my offer back to the community. I'm going to use this current version in my projects (my original reason for starting this), but other than that, it's honestly not in my interest to release/maintain a module.
So if you're interested, let me know, and we can take it from there!
And here's the full API:
//Construction
static BigD(from: number | string | boolean | bigint | Object): JSBD;
//Instance
get decimals (): number;
toString(radix?: number): string;
toLocaleString (locale?: string | string[], options?: Intl.NumberFormatOptions): string
toFixed (digits?: number): string;
toExponential (fractionDigits?: number): string;
toPrecision (precision?: number): string;
//Round
static round (value: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
//Conversion
static toNumber(x: JSBD): number;
static toBigInt (x: JSBD): bigint;
static toString (x: JSBD): string;
static toBoolean (x: JSBD): boolean;
static toSymbol (x: JSBD): symbol;
//Arithmetic
static add(lhs: JSBD, rhs: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
static subtract(lhs: JSBD, rhs: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
static multiply(lhs: JSBD, rhs: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
static divide(lhs: JSBD, rhs: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
static remainder(lhs: JSBD, rhs: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
static exponentiate(lhs: JSBD, rhs: JSBD, roundOpts?: DecimalRoundingOptions): JSBD;
//Comparison
static lessThan(lhs: JSBD, rhs: JSBD): boolean;
static lessThanOrEqual(lhs: JSBD, rhs: JSBD): boolean;
static greaterThan(lhs: JSBD, rhs: JSBD): boolean;
static greaterThanOrEqual(lhs: JSBD, rhs: JSBD): boolean;
static equal(lhs: JSBD, rhs: JSBD): boolean;
static notEqual(lhs: JSBD, rhs: JSBD): boolean;
//Strict unary operators
static unaryMinus(rhs: JSBD): JSBD;
static logicalNot (rhs: JSBD): boolean;
static logicalNot2 (rhs: JSBD): boolean;
//Bitwise
static bitwiseNot(rhs: JSBD): JSBD;
static bitwiseNot2(rhs: JSBD): JSBD;
static bitwiseOr(lhs: JSBD, rhs: JSBD): JSBD;
static bitwiseXOr(lhs: JSBD, rhs: JSBD): JSBD;
static bitwiseAnd(lhs: JSBD, rhs: JSBD): JSBD;
static bitwiseLeftShift(lhs: JSBD, rhs: JSBD): JSBD;
static bitwiseRightShift(lhs: JSBD, rhs: JSBD): JSBD;
//Loose operators
static EQ(lhs: any, rhs: any): boolean;
static NE(lhs: any, rhs: any): boolean;
static LT(lhs: any, rhs: any): boolean;
static LE(lhs: any, rhs: any): boolean;
static GT(lhs: any, rhs: any): boolean;
static GE(lhs: any, rhs: any): boolean;
static ADD(lhs: any, rhs: any): any;