You Probably Don't Know Of These 2 Math Methods in JavaScript: fround() and imul()

codeguage

Codeguage

Posted on February 27, 2024

You Probably Don't Know Of These 2 Math Methods in JavaScript: fround() and imul()

Table of contents

Introduction

When JavaScript was first released to the public in 1995 in the Netscape Navigator 2.0 browser, no one could've ever imagined the sheer level of complexity the language would tackle in the coming years.

Fast-forwards 25 years, JavaScript is being used for all different kinds of things, from gaming, to servers, to mathematical algorithms, to I/O, to system applications, to embedded systems (such as in Raspberry PI) — it's just everywhere!

In this regard, there has been a lot that the ECMA International community — the one behind churning all the nice bells and whistles into JavaScript — has come to realize over the years.

For example, back then, there were no features in the language to efficiently handle asynchronous programming. Then the world eventually got promises in ES6.

Similarly, JavaScript natively lacked an efficient way to organize code into different files — known as modules — for a very very long time. But, thanks to ES6 again, the world got ECMAScript modules, with the import and export constructs to power modular programming natively in this beautiful scripting language.

Now when we talk about the Math object, few might know that it too got a couple of advancements over these years, purely based on the sheer extent of complexity being handled by JavaScript.

In this article, we'll talk about two methods added to the Math object in JavaScript with the inception of ES6, following from some advanced usage of the language in different contexts.

These methods are fround(), for converting numbers to 32-bit floating-point numbers' precision, and imul(), for efficiently performing integer multiplication in a system optimized for efficiently running JavaScript.

Even if you're not going to use these methods in production (which especially holds for imul()), it's still good to know of them in order to understand a couple of concepts in programming in general, and solidifying your grasp of one of the most surprising languages of all time (that is JavaScript!).


What is Math.fround()?

Let's start off with the Math.fround() method which does have practical applications in userland code (i.e. code that we developers write).

Math.fround() takes a number and rounds it to the precision of a single-precision floating-point number.

The whole crux of understanding Math.fround() lies in understanding the single-precision floating-point format, common to all modern programming languages, including JavaScript.

So do you know about this format? Well, let's see what it is very quickly.

Recall that JavaScript stores every single number, be that an integral number or a number with a fractional part, internally as a floating-point number in the IEEE-754 double-precision floating-point format.

"Woah, that's a long name!" you say. It indeed is. Let's dissect it.

IEEE stands for Institute of Electrical and Electronics Engineers; in simple words, it represents a body that looks after standardizing disciplines and implementations of digital systems.

The number 754 represents an identifier for the floating-point numbers standard.

💡 Notice: IEEE — or better to say, IEEE SA (IEEE Standard Association) looks after multiple standards so they obviously need a way to distinguish between them.

The term 'double-precision' refers to the second of the two formats for storing floating-point numbers in memory, that is, in 64 bits. (The term 'double' specifically comes from the fact that this format spans double the memory of the single-precision format.)

Getting into the IEEE-754 double- and single-precision formats

In the IEEE-754 double-precision format, a number occupies 64 bits in memory, and is represented in terms of scientific notation: with a mantissa, an exponent, in addition to a sign.

Here's the distribution of these bits:

  • 52 bits for the mantissa
  • 11 bits for the exponent
  • 1 bit for the sign

We won't go into the details of how these work because there's quite a lot to cover, all of which is essentially out of the scope of this article.

What's important for now, for the Math.fround() method, is that a double-precision floating-point number has a precision of 53 bits (the additional 1 bit is implicit in the format).

This isn't the case for a single-precision number. In particular, in the IEEE-754 single-precision format, a number occupies 32 bits in memory, and is expressed again in terms of scientific notation.

The distributions of bits in this format is as follows:

  • 23 bits for the mantissa
  • 8 bits for the exponent
  • 1 bit for the sign

Notice how the single-precision format leads to numbers with much less precision than that of the double-precision format. To be square, a single-precision number has a precision of 23 bits.

Alright, this knowledge of both the double-precision and single-precision floating-point format has set us up to be able to explore the intuition behind fround(), so let's get right to it.

fround() converts a number in JavaScript to the precision of a single-precision floating-point number.

It's extremely important to note that the method does NOT return a single-precision number in JavaScript, so to speak; it returns back a number in the same double-precision format used all over JavaScript.

It's just that this returned double-precision number doesn't use its 53-bit precision to the full; it rather uses only 23 bits of precision (essentially, the rest of the bits just don't matter).

If you notice, this is indicated in the name of the method as well: it's 'fround' which means to merely 'round' a given number to the precision of the single-precision floating-point format.

In that sense, fround() has similarities to the Math.round() method.

💡 Notice: If you're curious about the meaning of the letter 'f' in fround, know that it comes from the word 'float' commonly used in programming languages to represent the single-precision floating-point number type, float. A similar type, double, represents the double-precision floating-point format.

Examples of fround()

Let's consider a couple of examples of using fround().

In the following snippet, we demonstrate one of the most popular arithmetic expressions in JavaScript, i.e. 0.1 + 0.2, in a typical way and then by rounding both numbers using Math.fround():

Output:

0.1 + 0.2 // 0.30000000000000004
Math.fround(0.1) + Math.fround(0.2) // 0.30000000447034836
Enter fullscreen mode Exit fullscreen mode

Notice how clear-cut discrepancy exists between the both numbers. In particular, the return value of 0.1 + 0.2 is much more precise (that is, much more closer to 0.3) than Math.fround(0.1) + Math.fround(0.2).

Let's consider another example, this time operating with the Math.PI constant:

Output:

Math.PI * 2 // 6.283185307179586
Math.fround(Math.PI) * 2 // 6.2831854820251465
Enter fullscreen mode Exit fullscreen mode

Once again, there is a very noticeable difference between the outcomes in both cases.

This testifies to the fact that floating-point arithmetic with the single-precision format can yield quite different results compared to when using the double-precision format.

So now that we know what fround() exactly does and how it works, it's time to consider its practical usage in applications.

Practical applications of fround()

Starting off with the good news, you won't almost ever be needing fround() in your JavaScript applications, even some of the most complex ones.

It's only when you know that you definitely need to work with the precision of a single-precision floating-point number that fround() makes sense.

For example, let's say you have a JavaScript program that reads a binary file containing a sequence of 32-bit single-precision floats and now want to process those integers in some way and then write the result back to the binary file in the respective locations.

Of course, you can do without using Math.fround() for processing these 32-bit floats. However, if producing consistent results is a requirement of the program, then you might want to re-consider this approach. Processing numbers using the double-precision format can produce results that are off by precision when compared to similar results obtained by processing single-precision numbers.

This isn't rocket science. It's pure logic. High-precision numbers when coerced to low-precision numbers might put the balance off than if we were using low-precision numbers from the get go.

Coming back to the example presented, if the JavaScript program reading the binary file requires consistency with the single-precision floating-point format, then we must leverage the Math.fround() method before performing any operation on the obtained numbers.

As simple as that.

As another example, let's say we're in a competitive programming setting and a particular question asks us to provide answers in the precision of the well-known float type in programming. As stated above, the float type is the analogue of 32-bit single-precision floats in programming languages.

Now, if we wish to solve the question using JavaScript (although real competitive programmers swear by C, C++ and Java, only), we'd clearly need to use the Math.fround() method, again before every single, concerned arithmetic operation that relates to the final answer.

"Why?" you ask. Well, because we want to make sure that our program doesn't produce any precision inconsistencies with the accepted answer.

💡 Notice: This example of a competitive programming problem is purely for the sake of explanation; usually, questions in competitive programming that rely on floating-point answers allow for a range of acceptable answers.

In short, while fround() isn't going to be one of the most used methods of the Math object for you in your JavaScript programs, it does have some important applications as specified here.

It should be in the arsenal of a real-pro JavaScript developer, especially the one who's going to deal with a lot of intensely mathematical and low-level algorithms.


What is Math.imul()?

The second usually-unknown method of Math is imul().

Before we begin, the good news with imul() is that you never need it in your code. Yes, that's right — never! Simple.

imul() is a helpful feature exclusively meant for JavaScript code produced by computer programs themselves. The typical example of this is Emscripten, a project to map low-level C/C++ code to asm.js, a strict subset of JavaScript.

So then why worry about understanding Math.imul()? Two reasons:

  • You'll understand some general programming concepts.
  • You'll get to appreciate one of the ways in which JavaScript engines approach optimizing programs specifically meant to be run as efficiently as possible.

So what is Math.imul()?

Math.imul() is used to multiply two given numbers together based on the semantics of multiplying 32-bit signed integers, as in languages such as C.

When we multiply two numbers in JavaScript, for instance 5 and 10, the multiplication operation internally resorts to floating-point multiplication, in this case giving 50.

32-bit integer multiplication is quite different from this.

In particular, if the product of the multiplication is beyond 232, it's reduced modulo 232 and then normalized based on its sign. In other words, it's always made sure that the result of the multiplication is within the range of a 32-bit integer.

💡 Notice: The integers we're talking about here are all signed 32-bit integers.

Examples of imul()

Shown below are a couple of Math.imul() calls on a bunch of numbers.

When the outcome of a multiplication operation is within the range of a signed 32-bit integer, the return value of Math.imul() is the same as the multiplication operator (*) in JavaScript, as follows:

Output:

Math.imul(2, 3) // 6
Math.imul(2, -1) // -2
Math.imul(105, 100) // 10500
Math.imul(-2, -9999) // 19998
Enter fullscreen mode Exit fullscreen mode

But when the outcome exceeds this range, Math.imul() reduces the result modulo 232 and then normalizes the final value (that is, making it negative in case it is greater than or equal to 231).

This is illustrated below:

Output:

Math.imul(1024, 2097152) // -2147483648
1024 * 2097152 // 2147483648
Enter fullscreen mode Exit fullscreen mode

Multiplying 1024 with 2097152 gives 231. After reducing 231 modulo 232 and the normalizing the result, we get -231. This describes the difference between the Math.imul() and the * operator calls.

But why would we ever want to convert numbers to 32-bit integers before multiplying them in JavaScript?

Let's find out.

Practical application of imul()

Why would we ever want to use Math.imul() in JavaScript?

As stated before, Math.imul() isn't meant to be used by userland code. In fact, if we go on and use it, we might get a performance hit by virtue of the conversion of each given (double-precision) number to an integer prior to the multiplication, and then conversion back to a double-precision number.

Math.imul() is a feature introduced exclusively for enforcing optimizations in JavaScript programs produced by other programs (which are out of the scope of this article).

Taking the example of Emscripten, when it converts a C/C++ program into JavaScript, the goal of the conversion is to run the resultant JavaScript code extremely efficiently.

💡 Notice: This goal makes sense because C and C++ are both statically-typed languages and, likewise, a ton of knowledge is already known about the flow of execution in the program and how variables interact with one another, what given operators mean, and so on. All of this knowledge can be in turn used to hint JavaScript engines to run the final JavaScript program superbly efficiently, paying off the ultimate power of C/C++ code.

Obviously, the converted code when run in JavaScript is much slower than C/C++ counterpart, but if we compare this code to a normal JavaScript program we'd typically write, it's much faster.

In this respect, one hint that JavaScript engines use to understand the nature of the multiplication operation is Math.imul().

Math.imul() in such optimized JavaScript code translates to quick low-level operations when compiled by an intelligent JavaScript engine, performing the multiplication of two given integers based on integer arithmetic.

This notion can be easily confirmed by going through the source code of Emscripten. Particularly, in the em_math.h file, which contains C code, head over to line 33, where the Math.imul() method is mentioned along with some useful information.

Here's the useful comment that precedes the series of methods containing Math.imul() in em_math.h:

The following operations [which include Math.imul()] have very fast WebAssembly opcodes.

If we inspect the file further, we see that it says that Math.imul() gets converted to the opcodes i32.mul and i64.mul. These codes can be seen in this WebAssembly Opcode Table.

And that's essentially it about Math.imul().

It's a feature exclusively meant for optimizing JavaScript engines for code that is typically not ever written by a human developer. It's good to know about it but not really good or any sensible to use it in userland code.


💖 💪 🙅 🚩
codeguage
Codeguage

Posted on February 27, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related