DEV Community

EmNudge
EmNudge

Posted on

Having Fun With Frustrations - myArr.map(parseInt)

I haven't written a post on here in quite a bit, having run out of immediate complex topics in JS to cover that haven't been done extensively and written better than I could ever write.

It was at a Svelte Conference in NYC, however, that I was given a new problem. I'd made a friend who had virtually no experience with the front-end and was regardless attending a frontend conference.

He mentioned a bit of WTFjs that his coworkers had previously brought up to him. I was super excited to have known exactly why it happened and thought it a great snippet of code to share here.

const myArr = [10.43242, 10.83223234, 10.3244, 10.4543, 10.3422, 10];
const newArr = myArr.map(parseInt);
console.log(newArr);
Enter fullscreen mode Exit fullscreen mode

What does this code do? Well first we have an array, called myArr, filled with numbers. They're all roughly equal to 10, each with some numbers after the decimal place. This array is changed using the map prototype method and newArr is set to its result. We then log newArr to the console.

What gets logged? Well let's try to predict that before we see the answer. We are passing the map method the function parseInt, which you may have read my article on over here.

window.parseInt()

parseInt is a function which takes a string and returns a number. If it gets a number, it will convert the number to a string before performing the coercion. Yeah, a bit roundabout, but it gets the job done.

How does it convert a string to a number? Well, there are quite a couple of rules, but most importantly, it removes any decimals. In this manner it is different than Math.floor(). Math.floor() will floor a number. That means if it is 3.52, it will return 3. If it is 1.9999, it will return 1.

Interestingly, if it is -0.1, it will return -1. It is flooring, not truncating. parseInt, however, will slice off anything past the decimal point and instead return -0 (yes, negative zero is a real thing).

Now, since map takes a function and applies this function to each element, we'd assume our new array to look something like [10, 10, 10, 10, 10, 10] and to see that displayed in the console. Instead what we get is:

[10, NaN, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Okay. Hmmmmm... That's... not at all what we thought would happen.
Using parseInt(10.2313) in the console, we see that 10 is returned. Are any of our decimals significant somehow? No, we also get 10 when we use any of those numbers specifically. The one thing we failed to mention is the radix.

The Radix

parseInt's main job is converting strings to numbers, not numbers to numbers. It does this via an optional radix which denotes what base the number is in. We usually operate in base 10, meaning that our number system uses 10 numbers - 0 through 9. In base 16, we also include A through F. In base 2, we only include 0 and 1.

This means if we pass parseInt "AF3" with the radix of 16, we will get 2803.

parseInt("AF3", 16) // -> 2803
parseInt("101", 2)  // -> 5
parseInt("11", 8)   // -> 8
parseInt("283", 10) // -> 283
Enter fullscreen mode Exit fullscreen mode

This radix is optional, so if we don't pass anything at all, we get a default of base 10. That's why we don't get any weird results we pass numbers to it regularly.

So why are getting these strange results? Well we haven't realized that map actually passes multiple parameters. If we look at the documentation using devdocs over here, we see that the format for the map method looks like the following:

const new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg])
Enter fullscreen mode Exit fullscreen mode

(Yes, I changed var to const. Gotta get rid of bad habits.)

We now see that map takes a function and an optional variable containing the this to use for the function.

The function for map's first parameter takes 3 parameters. Although the syntax shows them as optional, really all parameters are optional. It's up to us to take advantage of them, but they are all passed on each iteration regardless.

The first parameter is the current element in the array we are up to. The second is the current index, and the third is the entire array.
If map had only passed one parameter on each loop, we would have received what we expected. However, since it passes 3 parameters and parseInt accepts 2 of them, we are using the index as if it were a radix.

Let's run through the numbers

number radix result
10 0 10
10 1 NaN
10 2 2
10 3 3
10 4 4
10 5 5

What's interesting here is how 10 is a valid number for every radix except for 1. In a radix of 1, we only have 1 possible number - 0. Since 1 isn't valid in base 1, we get NaN. 0 will also return NaN, but that's because the spec defines that any radix less than 2 (but not 0) will return NaN.

For every other radix, 10 happens to be the number that comes right after they run out of numbers and must move to the second column.

That means that in base 2, the numbers go 0, 1, 10, 11, 100, etc. In base 3, the numbers go 0, 1, 2, 10, 11, 12, 100, etc. This pattern repeats for every base.

As for why we get 10 with a radix of 0, the JS spec clears this up.

Step number 8 and 9 goes as follows:

8. If R ≠ 0, then
    a. If R < 2 or R > 36, return NaN.
    b. If R ≠ 16, set stripPrefix to false.
9. Else R = 0,
    a. Set R to 10.

R in this case refers to the radix. If it's 0, we assume it's 10. Simple as that.

Conclusion

parseInt isn't a very good choice for numbers regardless, but if we wanted to use it, we could instead have written:

const myArr = [10.43242, 10.83223234, 10.3244, 10.4543, 10.3422, 10];
const newArr = myArr.map(num => parseInt(num));
console.log(newArr);
Enter fullscreen mode Exit fullscreen mode

In this code, the parameters are now specified. We can also now specify the radix of 10, which is known to be good practice.

In JS, we don't get errors when we pass too many parameters or not enough, which is why this error happens in the first place. Some linters may help you out here in that regard.

Top comments (0)