35

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?

6
  • 4
    What about Number(BigInt('x')) / Number(BigInt('y')) - that is, a plain native divide of two native numbers? Commented Jan 28, 2019 at 20:35
  • 1
    Otherwise as far as I can tell you cannot do that, as there is nothing related to BigInt as BigDecimal is related to BigInteger in Java Commented Jan 28, 2019 at 20:36
  • 6
    @Pointy Number(BigInt('12340000000000000001')) wouldn't work because the whole point of using a BigInt is to maintain the full precision which you'll lose if you just convert it to a number. The result of that would be 12340000000000000000. Commented Jan 28, 2019 at 20:40
  • 1
    @Pointy To your second comment... yeah, I imagine it might not be doable, but weird that the lib that is officially recommended as the fallback could do it</shrug> Commented Jan 28, 2019 at 20:41
  • 2
    The equivalent in JSBI would be JSBI.divide('12340000000000000000', '1000000000000000000') Commented Jan 28, 2019 at 20:51

2 Answers 2

48

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Now how can I do the same thing if I need 18 digit precision (e.g. down to .000000000000000001) if that would make that multiplier/divider in and of itself a BigInt?
See addition to my answer.
In typescript this will return ".12345678912345678987"
@trincot THANKS! my mistake was static decimals = 18; was not static
-1

Here's a helper function that cuts down both numbers by the same factor until they can be converted and divided without error.

function bigint_divide(/** @type {bigint} */ num, /** @type {bigint} */ dden) {
    let max_int = BigInt(Number.MAX_SAFE_INTEGER)
    let num_abs = num * (num > 0n ? 1n : -1n)
    let den_abs = den * (den > 0n ? 1n : -1n)
    let bigger_abs = num_abs > den_abs ? num_abs : den_abs
    let factor = 1n
    while(bigger_abs > max_int) {
        factor *= 10n
        bigger_abs /= 10n
    }
    num /= factor
    den /= factor
    return Number(num) / Number(den)
}

There may be precision lost by applying the factor but it's the only way to get to a floating point value somehow. This function mathematically does the same as the multiplication solution from the accepted answer, but finds a fitting factor itself without the caller having to worry about it.

6 Comments

"safe to handle" doesn't make much sense here.
Truncation introduces errors that you don't want.
Sorry @Bergi I don't understand what you mean by that. Yes indeed there may be precision lost while cutting down both numbers, but it's the only way to get to a floating point value somehow! With "safe to handle" I mean the division line in the very last line of the function. If the factor weren't applied beforehand, just dividing them as is may yield false values. This function mathematically does the same as the multiplication solution from the accepted answer, but finds a fitting factor itself without the caller having to worry about it. I'll put this into the answer itself.
I'm saying that it does yield false values. Try bigint_divide(BigInt(9e16), 9n), it should return 1e16 but doesn't. Truncation (via integer division) does introduce the error. Also it's unclear why you chose the specific cutoff to arrive at a "fitting factor", and no, this is not the same as in the accepted answer.
The cutoff value is Number.MAX_SAFE_INTGER, it doesn't get any more specific than that. I'm sorry but I'm not interested in continuing this conversation. Try a friendlier tone next time please. Aggressive elitism isn't helping anybody.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.