Search code examples
javascriptbigintegerbigint

How to divide two native JavaScript BigInt's and get a decimal result


Here's what I've tried so far. I'm looking to get a 12.34:

BigInt('12340000000000000000') / BigInt('1000000000000000000')

12n

Number(BigInt('12340000000000000000') / BigInt('1000000000000000000'))

12

FWIW, when I use the JSBI lib, it's working how I'd like:

JSBI.BigInt('12340000000000000000') / JSBI.BigInt('1000000000000000000');

12.34

Is that not possible natively?


Solution

  • You should multiply the numerator to accommodate the number of digits you need, perform the division and then divide with normal floating point division.

    var a = 12340000000000000000n;
    var b =  1000000000000000000n;
    
    console.log(Number(a * 100n / b) / 100);

    By only converting from BigInt to Number at the "end", you will lose the least precision.

    More precision

    If you need more than 16 digits precision and need decimals, then you'll need to throw your own implementation of a kind of BigDecimal API, or use an existing one.

    Here is a simple one using BigInt as its base type, combined with a configuration that determines how many digits (from the right) of each such BigInt should be interpreted as decimals (digits in the fractional part). That last information will for instance be used to insert a decimal separator when outputting the number as a string.

    class BigDecimal {
        // Configuration: private constants
        static #DECIMALS = 18; // Number of decimals on all instances
        static #SHIFT = 10n ** BigInt(BigDecimal.#DECIMALS); // Derived constant
        static #fromBigInt = Symbol();  // Secret to allow construction with given #n value
        #n; // the BigInt that will hold the BigDecimal's value multiplied by #SHIFT
        constructor(value, convert) {
            if (value instanceof BigDecimal) return value;
            if (convert === BigDecimal.#fromBigInt) { // Can only be used within this class
                this.#n = value;
                return;
            }
            const [ints, decis] = String(value).split(".").concat("");
            this.#n = BigInt(ints + decis.padEnd(BigDecimal.#DECIMALS, "0")
                                         .slice(0, BigDecimal.#DECIMALS));
        }
        divide(num) {
            return new BigDecimal(this.#n * BigDecimal.#SHIFT / new BigDecimal(num).#n, BigDecimal.#fromBigInt);
        }
        toString() {
            let s = this.#n.toString().replace("-", "").padStart(BigDecimal.#DECIMALS+1, "0");
            s = (s.slice(0, -BigDecimal.#DECIMALS) + "." + s.slice(-BigDecimal.#DECIMALS))
                   .replace(/(\.0*|0+)$/, "");
            return this.#n < 0 ? "-" + s : s;
        }
    }
    
    // Demo
    const a = new BigDecimal("123456789123456789876");
    const b = new BigDecimal( "10000000000000000000");
    
    console.log(a.divide(b).toString());

    Addendum: in a later Q&A I enriched this class with add, subtract, multiply and rounding features.