JavaScript: Poorly Designed?— Part 3: IEEE 754
— Previously in: JavaScript: Poorly Designed? — Part2: Coercion I explained how coercion is a powerful feature of the language rather than a “horrible way of doing things”. This following topic is usually coupled with the ridiculing of coercion so let’s continue the theme of common developer perceptions:
Ha ha, JavaScript struggles to add two numbers.
Let’s see if this is the case or if some may be a little misinformed about how computers do things in general. Surprisingly this topic does confuse many developers who even possess computer science degrees. This article is intended for those who are new to these caveats.
Floating Point Precision
JavaScript stores numbers in double-precision 64-bit binary format IEEE 754. Many programming languages use this format but statically typed languages also have types of numbers such as int, double, float, byte etc. JavaScript only has one number type “number”.
JavaScript will not automatically help you to reinterpret numbers when displaying to users for number sensitive scenarios, but its API will.
Why representational errors?
That’s the reality of computers vs humans. Humans generally work, shop and torture people via base 10. Computers are binary (base 2) hence the loss in precision when converting (we will explore this) and the term floating point precision. Here’s a breakdown of Representation Errors which despite the name are not actually errors per se, but a lack of precision.
The Big Short
It’s worth exploring floating-point calculations just a little to get some idea of the processes low-level compilers undergo to speak to us in our language. Bare in mind 64-bit floats uses 53-bit significand precision for JS.
I’m going to attempt to visualise part of the infamous 0.1 + 0.2 by simply converting one operand 0.1 to a binary representation:
…And so on, this is an infinite pattern but 64-bit precision is limited to 52 bits of significant precision (ignoring the sign).
In JavaScript 0.1 to binary format using toString with a radix of 2 produces:
Which is the same pattern we went through the notions of (Ignore the leading 0.)
Now let’s see what we get with 0.5:
Simple as there’s nothing to carry over once we multiply .5 by 2.
To fast track:
Hopefully, this helps to give a rough idea of how most numbers are not always as easy as dealing with let’s say 0.5.
tldr; You only need to prepare for this kind of behaviour if you are managing numbers that we use or see in the real world.
What seems to be the problem officer?
The problem is the misconception that arises by those who have been shielded from having to manage the lack of precision in computing (whatever that may mean).
In the context of number precision, UI development is essentially:
- Receiving the user’s inputs
- Populating content & making things interactive
- Monitoring the user’s behaviour
For the majority of calculations, we perform on the web, the precision tolerance of IEEE 754 is more than sufficient.
For situations where we need accuracy such as:
- Displaying Currency
- Transactions and quantities
Like many things in JavaScript, there are no training wheels, you need to manage these aspects yourself and simply give in to the quirks of scientific notation.
Managing representational errors
Let’s clarify a few scenarios:
- Unnecessarily re-interpreting numbers hinder performance. For calculations internal to the program (animations, SVG, canvas, WebGL etc) native numbers work fine.
- If you are displaying number content to a user, performing sensitive calculations for orders, purchasing, logistics or similar then built-in methods such as toPrecision and Math.x are generally sufficient. Intuition will let you know the significance of the numbers you’re dealing with.
- In addition to #2 you can easily break up numbers in JavaScript via strings since JavaScript strings are Array-like. toString, toFixed, isInteger, are also useful Number and Number.prototype methods.
- If native precision lacks significance, various libraries can deliver more accuracy within all programming languages but they should be avoided unless necessary (e.g. a calculator app requires precision).
Conclusion
It’s kind of fascinating that JavaScript has a dynamically-typed system, with just one number type that can represent several. This is a light-weight design choice for a light-weight platform. The choice to use IEEE-754 was due to the creator Brendan Eich’s tight deadline. I asked Brendan to clarify and he said:
It turns out I had time and plenty of C-like language parser writing experience to include bitwise-logical and shift ops (including >>> from Java). These created asm.js’s “type casts” to int32 and uint32, from the start. But yes: number as Java double was simplest under deadline.
It’s really not difficult to manage numbers via vanilla JavaScript, we are just lacking a few prominent leaders and mentors in the JS community to show newcomers best practises rather than resorting to “The language is broken” or “That’s why I use TypeScript”.
Feel free to pull me up on any mistakes I may have made, I’m not claiming to be an expert.
Thanks for the read… Next up: JavaScript: Poorly Designed? — Part 4: Object-based, Higher-order, Functional Programming