V8 (or other JS engine) BigInt Implementation - Displaying as Decimal - javascript

I'm wondering if someone might be able to explain a specific aspect of the JavaScript BigInt implementation to me.
The overview implementation I understand - rather than operating in base 10, build an array representing digits effectively operating in base 2^32/2^64 depending on build architecture.
What I'm curious about is the display/console.log implementation for this type - it's incredibly fast for most common cases, to the point where if you didn't know anything about the implementation you'd probably assume it was native. But, knowing what I do about the implementation, it's incredible to me that it's able to do the decimal cast/string concatenation math as quickly as it can, and I'm deeply curious how it works.
A moderate look into bigint.cc and bigint.h in the Chromium source has only confused me further, as there are a number of methods whose signatures are defined, but whose implementations I can't seem to find.
I'd appreciate even being pointed to another spot in the Chromium source which contains the decimal cast implementation.

(V8 developer here.)
#Bergi basically provided the relevant links already, so just to sum it up:
Formatting a binary number as a decimal string is a "base conversion", and its basic building block is:
while (number > 0) {
next_char = "0123456789"[number % 10];
number = number / 10; // Truncating integer division.
}
(Assuming that next_char is also written into some string backing store; this string is being built up from the right.)
Special-cased for the common situation that the BigInt only had one 64-bit "digit" to begin with, you can find this algorithm in code here.
The generalization for more digits and non-decimal radixes is here; it's the same algorithm.
This algorithm runs sufficiently fast for sufficiently small BigInts; its problem is that it scales quadratically with the length of the BigInt. So for large BigInts (where some initial overhead easily pays for itself due to enabling better scaling), we have a divide-and-conquer implementation that's built on better-scaling division and multiplication algorithms.
When the requested radix is a power of two, then no such heavy machinery is necessary, because a linear-time implementation is easy. That's why some_bigint.toString(16) is and always will be much faster than some_bigint.toString() (at least for large BigInts), so when you need de/serialization rather than human readability, hex strings are preferable for performance.
if you didn't know anything about the implementation you'd probably assume it was native
What does that even mean?

Related

Native support for 64 bit floating point in Javascript

As the question here states, even the newest Ecmascript 8 has no support for 64bit integers.
But the stage 3 proposal for bigInt looks promising and I expect it will be added to the Js spec sometime soon.
However, Even according to the proposal, we have to use a special constructor for declaring big numbers. (Q1) What is the technical reason behind, being unable to represent big numbers in the general way?
let bigNum = 2 ** 64 // Why can't JS do this without losing precision? (at least in future)
I know that JavaScript represents all numbers using IEEE-754 double-precision (64 bit) floating points and that this causes the problem.
(Q2) Why can't Javascript represent all numbers using some other standard which doesn't lose precision?
(Q3) What complications would arise if Javascript actually did that?
Edit: As stated by T.J, we can use a suffix instead of a constructor, but I still feel that is not exactly the general notation
(Q1) What is the technical reason behind, being unable to represent big numbers in the general way?
Breaking the web. The fundamentals of JavaScript numbers cannot be changed now, 20-odd years after they were originally defined. There's also the issue of performance: JavaScript's current numbers (IEEE-754 binary double [64-bit] precision) are very fast floating point thanks to being built into CPUs and math coprocessors. The cost of that speed is precision; the cost of arbitrary precision (or a dramatically larger precise range) is performance.
Someday in the future, perhaps JavaScript will get IEEE-754 64-bit or even 128-bit decimal floating point numbers (see here and here), if those formats (introduced in 2008) diffuse into the ecosystem and get hardware support. But that's speculation on my part. :-)
(Q2) Why can't Javascript represent all numbers using some other standard which doesn't lose precision?
See Q1. :-)
(Q3) What complications would arise if Javascript actually did that?
See Q1. :-)
Even according to the proposal, we have to use a special constructor for declaring big numbers.
If you want 64-bit specifically. If you just want BigInts, the proposal includes a new notation, the n suffix, for that: 2n is a BigInt 2. So with BigInts, your example would be
let bigNum = 2n ** 64n;

Implications of the unique binary representation for NaN in JavaScript

Would I be correct in assuming that the reason that JavaScript only supports one binary representation for NaN is that it allows interpreters to speed up operations involving NaNs by checking for that specific bit pattern as a 64 bit integer rather than rely upon the FPU handling them?
Stephen Canon's comment spurred me to run some timing tests, which I guess I should have done in the first place. I'm posting the results as an answer in case they're of interest to anyone else...
Using my Intel Core2 Quad CPU (2.33GHz) PC I compared
for(i=0;i<100000000;++i) x += 1.0;
in C++ and JavaScript with x first equal to 0.0 and then to NaN.
C++ x==0.0: 124ms
C++ x==NaN: 11888ms
JS x==0.0: 268ms
JS x==NaN: 432ms
So it seems that JavaScript is exploiting the fact that it already has to dynamically dispatch arithmetic operators to treat NaN as a special case.
I'm guessing that it's a hidden type that has no data, hence only one binary representation for NaN.

Roundoff error between Visual Basic and Javascript

I found a VB script in an Excel file that implements Runge-Kutta integration for solving a differential equation. I converted the implementation line by line to Javscript. However, the Javascript program does not yield the exact same results as the VB one... The numbers are close enough to be marginally acceptable, but I trust that the VB implementation is correct as it was part of a PhD Dissertation. So this leads me to believe that my Javascript implementation, running on NodeJS, suffers from some rounding ghosts.
In particular, I have noticed that as I continually add 0.01 to my time counter, the values are eventually unable to be represented properly, and I get values like 5.299999999999999934 instead of 5.3. This makes sense to me, as I have read about this quirk of Javascript in some books.
My Questions Are
Is my observation correct that Visual Basic does not suffer from this same precision shortcoming?
Does this mean plain vanilla Javascript math is inherently less accurate than plain vanilla VB math? (Without using other math libraries).
If I clamp my time variable, as in, force the value to be 5.30, instead of 5.299999999, will this actually make my results more correct?
edit: Using a Math.round(X*100)/100 clamp changes the output of all my time values to have only 2 decimal places, but does not actually change the values of any computations.
JavaScript uses IEEE 754 double-precision floating-point numbers...just like Visual Basic. The only difference is that JavaScript uses doubles for all numbers (that is, it doesn't have an integer type, or a single-precision float).
If a calculation in VB uses numbers of type Double in its calculations, it should have the same accuracy and precision as the equivalent calculation in JavaScript.
The upshot is that VB is in no way more accurate with respect numerical calculations than JavaScript. Whenever you're dealing with floating-point values, though, you have to be careful.
You should probably read David Goldberg's excellent article, What Every Computer Scientist Should Know About Floating-Point Arithmetic.
The chance of errors multiplying is increased with the number of summations, and there are a LOT of summations in techniques like Runge-Kutta, so the algorithm has to be constructed very carefully to take this into account. The definitive book on the subject is the time-tested Numerical Recipes, which covers Runge-Kutta, and takes into account floating-point errors.

Has any one faced Math.js auto approximation issue and got any work around for this?

Has any one faced Math.js auto approximation issue and got any work around for this?
If I enter any number more than 18 digits then this library returns the approximate value; not the exact value. Lets say if user enters "03030130000309293689" then it returns "3030130000309293600" and when user enters "3030130000309293799" even it returns "3030130000309293600". Can we stop this approximation? This is a bug or if not then how can I avoid approximation?
Due to this approximation if any user enters "03030130000309293695 == 03030130000309293799" then it will always return true which is totally wrong.
github -- https://github.com/josdejong/mathjs
We can try this at http://mathjs.org/ ( in Demo notepad).
This is released for production!
I think if any time user enters like "03030130000309293695 == 03030130000309293799" both side number only then we can do string comparison. Rest all cases will be taken care by approximation.Why I am saying this is because if i use the same library for "73712347274723714284 *73712347274723713000" computation then it gives result in scientific notation.
03030130000309293695 and 03030130000309293799 are pretty much the same number.
HOW?
According to this answer the limit of JS number is 9007199254740992(2^53). Your both numbers are greater than this number and so precision is left out. You probably need to use library like Big.js
It's not a library issue, it's just language architecture issue. You can even open your browser console and type in your equation to see it it truthy.
This is not really a problem of Math.js but a result of how numbers work in javascript. Javascript uses 64bit binary floating point numbers (also known as 64bit double in C). As such, it has only 53 bits to store your number.
I've written an explanation here: Javascript number gets another value
You can read the wikipedia page for 64 bit doubles for more detail: http://en.wikipedia.org/wiki/Double_precision_floating-point_format
Now for the second part of your question:
If not then how can I avoid approximation?
There are several libraries in javascript that implements big numbers:
For the browser there's this: https://github.com/MikeMcl/bignumber.js
Which is written in pure javascript. Should also be usable node.js.
For node.js there's this: https://github.com/justmoon/node-bignum
Which is a wrapper around the big number library used by OpenSSL. It's written in C so can't be loaded in the browser but should be faster and maybe more memory efficient on node.js.
The latest version of math.js has support for bignumbers, see docs:
https://github.com/josdejong/mathjs/blob/master/docs/datatypes/bignumbers.md

Python vs Javascript floating point arithmetic giving very different answers. What am I doing wrong?

Python version | Javascript version | Whitepaper
So, I'm working on a website to calculate Glicko ratings for two player games. It involves a lot of floating point arithmetic (square roots, exponents, division, all the nasty stuff) and I for some reason am getting a completely different answer from the Python implementation of the algorithm I translated line-for-line. The Python version is giving basically the expected answer for the example found in the original whitepaper describing the algorithm, but the Javascript version is quite a bit off.
Have I made an error in translation or is Javascript's floating point math just less accurate?
Expected answer: [1464, 151.4]
Python answer: [1462, 155.5]
Javascript answer: [1470.8, 89.7]
So the rating calculation isn't THAT bad, being 99.6% accurate, but the variance is off by 2/3!
Edit: People have pointed out that the default value of RD in the Pyglicko version is 200. This is a case of the original implementer leaving in test code I believe, as the test case is done on a person with an RD of 200, but clearly default is supposed to be 350. I did, however, specify 200 in my test case in Javascript, so that is not the issue here.
Edit: Changed the algorithm to use map/reduce. Rating is less accurate, variance is more accurate, both for no discernible reason. Wallbanging commence.
typically you get errors like this where you are subtracting two similar numbers - then the normally insignificant differences between values become amplified. for example, if you have two values that are 1.2345 and 1.2346 in python, but 1.2344 and 1.2347 in javascript, then the differences are 1e-4 and 3 e-4 respectively (ie one is 3x the other).
so i would look at where you have subtractions in your code and check those values. you may find that you can either (1) rewrite the maths to avoid the subtraction (often it turns out that you can find an expression that calculates the difference in some other way) or (2) focus on why the values at that particular point differ between the two languages (perhaps the difference in pi that the other answer identified is being amplified in this way).
it's also possible, although less likely here, that you have a difference because something is treated as an integer in python, but as a float in javascript. in python there is a difference between integers and floats, and if you are not careful you can do things like divide two integers to get another integer (eg 3/2 = 1 in python). while in javascript, all numbers are "really" floats, so this does not occur.
finally, it's possible there are small differences in how the calculations are performed. but these are "normal" - to get such drastic differences you need something like what i described above to occur as well.
PS: also note what Daniel Baulig said about the initial value of the parameter rd in the comments above.
My guess is that involves the approximations you're using for some of the constants in the JavaScript version. Your pi2 in particular seems a little.. brief. I believe Python is using doubles for those values.

Categories