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);
``````

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]
``````

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`.

`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
``````

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])
``````

(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

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);
``````

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.