Hacker News new | ask | show | jobs
by mschuetz 2588 days ago
> I once wrote a replacement for the + operator in javascript because computers can't do proper addition (0.1+0.2!=0.3).

That's normal floating point math behaviour and fine in most use cases. You can't fix this for add, sub, mul and div without serious performance implications.

> Would it be that much slower, that it's an awful idea to do by default?

Yes. Easily an order of magnitude slower, perhabs two. The fixed size of uint32, float, double, etc. is an important property that allows computations to be fast. And since the size is fixed, you have to accept trade-offs in how or which numbers can be represented. Also, as someone else already mentioned, you can't even store 1 / 3 as a single numeric value. You'd have to store it as a rational number. Things are going to get super complicated once you combine rational numbers in computations and complex formulas.

I'm doing lot's of number crunching tasks with javascript, with performances of up to almost ~50% of equivalent C++ code. If js would have used a non-natively supported number format by default, it would have been useless for me.

1 comments

I did some testing with pypy, which also works with arbitrarily large integers but iirc does JIT instead of interpretation (like cpython would do), so that should be similar to JS in V8 except that it has arbitrarily large integers.

    a = Math.pow(2, 1023)
    t = new Date().getTime()
    for (var i = 0; i < 1e7; i++) {
        a += i;
    }
    console.log(new Date().getTime() - t);
vs

    a = pow(2, 1024)
    t = time.time()
    for i in range(int(1e7)):
        a += i
    print(time.time() - t)
Both ran a bunch of times: pypy does it in 279ms and nodejs in 250. I chose 1024 for Python because that is where JS starts to return infinity, so the JS code does operations on a number just below that. The time seems to be spent in the loop, as an empty loop or a loop doing a+=0 is 20x faster.

Lowering the exponent to 100, JS spends 269ms and pypy 142. Not sure why that is, but having arbitrarily large integers doens't seem to make this arithmetic any faster.

I don't know how to quickly toy around with fraction-based floats, but at least for arbitrarily large integers, I'm not sure why we're going to have to put up with new syntax.

25 nanoseconds is much longer than a normal double-precision addition, loop counter increment, and conditional jump back to the top of a loop should take, so there's something other than the time taken by additions that's going on in your benchmark. I'm not a Node.JS expert, but I suspect it's not getting JITted properly, or getting poorly optimized if so.

I tried in C:

    #include <math.h>
    #include <stdio.h>
    int main()
    {
      double a = pow(2, 100);
      for (double i = 0; i < 1e7; ++i) {
        a += i;
      }
      printf("%f\n", a);
    }
and timed it. The time taken was 17ms.
Rearanged your js sample a bit, now it runs in ~26ms (first time) and ~11ms (subsequent times) instead of ~220ms in the chrome developer console.

    {
        let a = Math.pow(2, 1023)
        let t = performance.now();
        let max = 1e7;
        for (let i = 0; i < max; i++) {
         a += i;
        }
        console.log(performance.now() - t);
    }
Main problem was, that you should declare a with let.

That benchmark is a bit strange/flawed anyway. You're initializing a as pow(2, 1023), then adding numbers in the loop. But since a is already such a large double value, the result won't change. The numbers you add are too small to make a dent in the value of a, likely because a isn't an integer. It's a double with a limited precision for large integer values.