Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

I Prefer The Unary Plus (+) Operator Over parseInt() And ParseFloat() When Coercing Strings To Numbers In JavaScript

By Ben Nadel on

Yesterday, I accidentally stumbled into the location of a bug that's existed in InVision for over 5-years. And, when I finally found the problematic line of code, I felt like it warranted a write-up on its own. Because, the "broken" code wasn't inherently broken; rather, it was just using an approach that was more prone to subtle failures. The line of code in question was using parseInt() to try and coerce a String value into a Number value. For this type of operation, I almost always prefer the unary plus (+) operator over parseInt() or parseFloat() in JavaScript.

To be clear, I am not saying that the unary plus operator is equivalent to the parseInt() or the parseFloat() methods. The unary plus operator and these methods work in different ways and have different features. What I am saying is that in the most common use-cases, I believe that the unary plus operator more clearly represents the intent of the engineer writing the code; and, is less prone to subtle failures when faced with unexpected inputs.

To paint a picture of the bug I found, here's the line of React code that I found:

parseInt( $target.attr( 'data-group-id' ), 10 )

As you can see, it was just using a jQuery reference to grab an attribute out of the DOM (Document Object Model); and then, parse that attribute value into a number. The problem with this code (in my application) is that there's an edge-case in which the given attribute contains a temporary UUID. Something like:

cb34d-234ks-2343f-00xj

Now, if you try to run this value through parseInt(), you get NaN (Not a Number):

parseInt( "cb34d-234ks-2343f-00xj", 10 ) => NaN

But, the UUID is generated with random digits. Which means, passing the UUID to the parseInt() function will "fail" in different ways depending on the characters in the UUID. For example, if we try parsing another randomly-generated UUID, we end up with a number:

parseInt( "997da-00xj-2343f-234ks", 10 ) => 997

Both the parseInt() and the parseFloat() methods parse the input string until they hit a non-numeric value, at which point they discard the rest of the input. So, to be clear, this is parseInt() working exactly how it was designed to work.

The problem is, this is almost certainly not what the original engineer intended. The intent of the engineer was simply to convert a String into a Number; and, if the String didn't represent a Number, the resultant value should be NaN.

In the vast majority of cases in which I need to convert a String to a Number, I've learned to prefer the unary plus operator because it's much short from a syntax perspective and it more closely maps to the intention of type coercion:

// When used with a temporary UUID, the characters in the string don't matter -
// the unary plus operator will always return NaN.
console.log( +"cb34d-234ks-2343f-00xj" ); // => NaN
console.log( +"997da-00xj-2343f-234ks" ); // => NaN

// The unary plus operator works with both integer and float inputs.
console.log( +"16" ); // => 16
console.log( +"3.14159" ); // => 3.14159

Now, it's worth noting that the unary plus operator in JavaScript also has some subtle behaviors. As I learned from this Scotch.io post, the unary plus operator can parse special string formatting:

// It will parse scientific notation that contains "e".
console.log( +"1.2345e10" ); // => 12345000000

// It will parse hex values that start with "0x".
console.log( +"0xff" ); // => 255

// It will parse Infinity.
console.log( +"Infinity" ); // => Infinity
console.log( +"-Infinity" ); // => -Infinity

In my mind, however, this is still different than the behavior of parseInt() and parseFloat() because these string inputs are at least intended to represent a number. And, unlike parseInt() and parseFloat(), the unary plus operator will still return NaN when the special input contains any unexpected characters:

console.log( +"1.2345e10___" ); // => NaN
console.log( +"0xff___" ); // => NaN
console.log( +"Infinity___" ); // => NaN
console.log( +"-Infinity___" ); // => NaN

So, going back to the bug that I found in the React code, all I had to do in order to fix the issue was replace the parseInt() call with the unary plus operator:

+$target.attr( 'data-group-id' )

In my JavaScript programming, parseInt() and parseFloat() still have a time and a place. For example, I still use parseInt() to parse base-16 input (hex) into decimal value, such as with color conversion. However, in the vast, vast majority of cases in which I have to coerce a String value into a Number value in JavaScript, I prefer the unary plus operator. It's short; it fails more consistently; and, I believe it maps more closely to my intent.



Reader Comments

@All,

After writing this, I've been struggling a bit with why I don't put a space after the + sign. For example, if I were to use one of the other unary operators, like the ! or !! (which really isn't an operator in and of itself), I would always put a space between the operator and the operand). Example:

if ( ! values.length ) {
	// ....
}

I would never in a million years place the ! operator up against its operand like !values.length ... so, why am I placing the + operator up against its operand?

In fact, adding the space may help draw attention to it, which may make it more obvious:

var value = + "0xff";

// ...vs...

var value = +"0xff";

Hmmm. Of course, it starts to get weird if you're ever going to use the plus binary operator along side the unary operator:

var value = ( 3 + + "0xa" );

Of course, you could just argue that combining this into a single-statement is bad form, regardless of whether or not it is "correct".

Hmmm, something to think about. I'll have to play around with it a bit more in real-world code to get a sense of whether or not the operator feels ok being pulled-out a space.

Reply to this Comment

@All,

Worth mentioning, a number of people on Twitter responded that they like to use the Number() constructor for such things. As in:

console.log( Number( "cb34d-234ks-2343f-00xj" ) ); // => NaN
console.log( Number( "997da-00xj-2343f-234ks" ) ); // => NaN
console.log( Number( "3" ) ); // => 3
console.log( Number( "0xff" ) ); // => 255
console.log( Number( true ) ); // => 1
console.log( Number( false ) ); // => 0
console.log( Number( "" ) ); // => 0
console.log( Number( null ) ); // => 0

I don't think there's anything wrong with that. To be honest, I just don't have a lot of experience using the native constructors in my day-to-day JavaScript. Probably, I have the most experience with RegExp(); however, I still prefer to use the literal, /pattern/flags, when possible.

So, anyway, I still prefer the unary operator; but, I thought it was worth sharing the Number() approach as well.

Reply to this Comment

Hi Ben, Nice find. I saw your tweet on twitter but was not able to grasp it. This article explains it nicely. Personally I prefer using + instead of Number constructor.

Reply to this Comment

@JM,

While I would generally suggest taking benchmarks with a grain-of-salt, I think it make sense that the + operator is faster than Number(). I believe - and hopefully I am not misremembering here - that there is actually a difference between a primitive value like 3 and an Object like the one produced by Number( 3 ). From what I believe I have read, JavaScript uses the primitive values in the code; and then, as needed, will automatically wrap the primitive value in the relevant constructor when it needs to do things like access member methods.

So, what I think that means it that when you do something like this in JavaScript:

console.log( 3.1415.toFixed( 2 ) ); // => 3.14

... the JavaScript interpreter is actually implicitly wrapping it to be something like:

console.log( Number( 3.1415 ).toFixed( 2 ) ); // => 3.14

But don't quote me on this :P All to say, I assume / suspect that the + operator is generating a primitive value, not a Number instance.

Reply to this Comment

I prefer using Number('100'), because it's an explicit function call, so it's easier to read. It's functionally identical to unary + ... but using the unary operator is much harder to read and feels like a hack.

There is a microscopic performance advantage to +, if that outweighs code quality, but that's the only difference I can find.

A separate thing I wanted to mention, is that Number/String/Boolean should only ever be treated as casting functions. I have never seen a valid use case for using new Number/String/Boolean, which is what gives you a boxed value. This post explains it pretty well ... new Number(x) is bad, Number(x) is good, +x is a shortcut.
https://stackoverflow.com/a/4719334/272072

Reply to this Comment

@Scott,

Very interesting. I had always just assumed that the new was quasi-optional for the built-in constructors. Meaning, I thought RegExp() and new RegExp() were essentially doing the exact same thing. It's good to know that they are in fact different.

I'll have to mull-over how I feel about calling the casters. I'm not opposed to it - I'm just not used to it. For example, I use the !! double-not operator to case to Boolean all the time. As in:

var hasValues = !! values.length;

I've been doing that for so long that the idea of converting to the more explicit cast feels funky:

var hasValues = Boolean( values.length );

I'll let that marinate in the back of my mind. Thanks for the clarity :D

Reply to this Comment

@Jcon,

Ah, excellent catch! Damn that compiler, always trying to help :P That said, that's exactly why I don't put tooooo much heed into micro-bench-marks. In the end, you often just need to go with developer ergonomics. But, it's great to see how roughly equivalent they are.

Reply to this Comment

@Ben,

"I would never in a million years place the ! operator up against its operand like !values.length"

Why in the world not? That's just... utterly bizarre.

Reply to this Comment

@Rabo,

Just personal preference. With the ! up against the operand, it just looks wrong to me. I generally prefer more white-space where is makes sense. But, I know not everyone is white-space-sensitive.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.