107

Wonder if there are any nontrivial ways of finding number's sign (signum function)?
May be shorter / faster / more elegant solutions than the obvious one

var sign = number > 0 ? 1 : number < 0 ? -1 : 0;

Short answer!

Use this and you'll be safe and fast (source: moz)

if (!Math.sign) Math.sign = function(x) { return ((x > 0) - (x < 0)) || +x; };

You may want to look at performance and type-coercing comparison fiddle

Long time has passed. Further is mainly for historical reasons.


Results

For now we have these solutions:


1. Obvious and fast

function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }

1.1. Modification from kbec - one type cast less, more performant, shorter [fastest]

function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }

caution: sign("0") -> 1


2. Elegant, short, not so fast [slowest]

function sign(x) { return x && x / Math.abs(x); }

caution: sign(+-Infinity) -> NaN, sign("0") -> NaN

As of Infinity is a legal number in JS this solution doesn't seem fully correct.


3. The art... but very slow [slowest]

function sign(x) { return (x > 0) - (x < 0); }

4. Using bit-shift
fast, but sign(-Infinity) -> 0

function sign(x) { return (x >> 31) + (x > 0 ? 1 : 0); }

5. Type-safe [megafast]

! Seems like browsers (especially chrome's v8) make some magic optimizations and this solution turns out to be much more performant than others, even than (1.1) despite it contains 2 extra operations and logically never can't be faster.

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
}

Tools

Improvements are welcome!


[Offtopic] Accepted answer

  • Andrey Tarantsov - +100 for the art, but sadly it is about 5 times slower than the obvious approach

  • Frédéric Hamidi - somehow the most upvoted answer (for the time writing) and it's kinda cool, but it's definitely not how things should be done, imho. Also it doesn't correctly handle Infinity numbers, which are also numbers, you know.

  • kbec - is an improvement of the obvious solution. Not that revolutionary, but taking all together I consider this approach the best. Vote for him :)

disfated
  • 10,633
  • 12
  • 39
  • 50
  • I think your answer is faster and shorter than all others here. You can make it a little faster `number > 0 ? 1 : number !== 0 ? -1 : 0`. I presume that testing equality is faster than the inequality, although that's a big claim and is both implementation and hardware dependent so would need to be well tested in order to be verified. – davin Oct 02 '11 at 07:21
  • I would better use `==` instead of `===` as it must be a bit faster in this case. – disfated Oct 02 '11 at 11:11
  • what is the point of having `0` as special case ? – c69 Oct 02 '11 at 15:39
  • 3
    the point is that sometimes `0` is a special case – disfated Oct 04 '11 at 15:03
  • There is a difference between my solution an yours. There is no need to check that number is greater than zero because 0==true so if number equals to false and isn't less than 0 then it's greater. One check less can give more efficiency, additionaly typed in pretty-perfectionists way. – kbec Jan 23 '13 at 23:39
  • I get what you mean. The key word here is "obvious". So I want it to be really obvious. I hope you agree that your solution is a bit less obvious. Also, it has the same number of checks - 2, but it is really more performant because of removal of one type cast (`x > 0 ?` - casted to number, `x ?` - not). So if you do not mind I'll put your solution as a "modification" of the first one. – disfated Jan 29 '13 at 09:33
  • 1
    I've made a set of JSPerf tests (with different kinds of input) to test every algorithm, which can be found here: http://jsperf.com/signs **The results may not be as listed in this post!** – Alba Mendez Mar 02 '13 at 13:08
  • @jmendeth, i've ran your jsperf and "safe" was 3 times faster than "sign1b", which is nonsense. never liked jsperf :) – disfated Mar 09 '13 at 14:25
  • 2
    @disfated, which one of them? Of course, if you run the `test everything` version, Safe will refuse to test the special values, so it will be faster! Try running the [`only integers`](http://jsperf.com/signs-integer) test instead. Also, JSPerf is just doing his job, it isn't a matter of liking it. :) – Alba Mendez Mar 10 '13 at 09:47
  • 1
    This is a lovely question – iono Mar 31 '13 at 10:48
  • 1
    Whoa, my approach is slow! Thanks for testing; would never have guessed. Funnily, (x>0)-(x<0) is how we've been doing it in early 2000s in ACM ICPC because it was _the fastest solution available_ (inline assembler being banned by the rules) and also one that's obviously correct. – Andrey Tarantsov Dec 11 '13 at 11:19
  • 2
    According to jsperf tests it turns out that `typeof x === "number"` puts some magic on performace. Please, make more runs, especially FF, Opera and IE to make it clear. – disfated Dec 13 '13 at 01:15
  • 4
    For completeness I added a new test http://jsperf.com/signs/7 for `Math.sign()` (0===0, not as fast as "Safe") which appeared in FF25 and is upcoming in chrome. – Alex K. Jan 07 '14 at 12:40
  • @AndreyTarantsov: That may be because in JS the `<` and `>` operators return objects of type boolean (unlike C, which returns an int 0 or 1 I think), so two type conversions have to be performed in order to do the subtraction. – rvighne Jun 19 '14 at 16:28
  • Does the accepted answer (no. 5) take fractions into account? It seems as if answers with "Undefined" when x<0.5 ? – Ideogram Jun 20 '14 at 20:05
  • @Ideogram, it does. Could you provide exact `x` value, which as you suppose leads to wrong results? – disfated Jun 20 '14 at 22:06
  • 6. The Native: `Math.sign` – Derek 朕會功夫 Sep 06 '14 at 19:05
  • The performance is probably caused by the fact that you are discarding objects (`new Number()`) so the rest of the code only have to handle literals. Depending on your scenario you could drop the support for objects, I wouldn't. – Knu Mar 14 '15 at 03:43

16 Answers16

82

More elegant version of fast solution:

var sign = number?number<0?-1:1:0
Ricardo Rocha
  • 14,612
  • 20
  • 74
  • 130
kbec
  • 3,415
  • 3
  • 27
  • 42
29

Dividing the number by its absolute value also gives its sign. Using the short-circuiting logical AND operator allows us to special-case 0 so we don't end up dividing by it:

var sign = number && number / Math.abs(number);
Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
25

The function you're looking for is called signum, and the best way to implement it is:

function sgn(x) {
  return (x > 0) - (x < 0);
}
disfated
  • 10,633
  • 12
  • 39
  • 50
Andrey Tarantsov
  • 8,965
  • 7
  • 54
  • 58
  • 3
    Wait. There's mistake: for (x = -2; x <= 2; x++) console.log((x > 1) - (x < 1)); gives [-1, -1, -1, 0, 1] for (x = -2; x <= 2; x++) console.log((x > 0) - (x < 0)); gives correct [-1, -1, 0, 1, 1] – disfated Jan 20 '13 at 14:49
16

Should this not support JavaScript’s (ECMAScript’s) signed zeroes? It seems to work when returning x rather than 0 in the “megafast” function:

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? x : NaN : NaN;
}

This makes it compatible with a draft of ECMAScript’s Math.sign (MDN):

Returns the sign of the x, indicating whether x is positive, negative or zero.

  • If x is NaN, the result is NaN.
  • If x is −0, the result is −0.
  • If x is +0, the result is +0.
  • If x is negative and not −0, the result is −1.
  • If x is positive and not +0, the result is +1.
Martijn
  • 3
  • 1
  • 5
12

For people who are interested what is going on with latest browsers, in ES6 version there is a native Math.sign method. You can check the support here.

Basically it returns -1, 1, 0 or NaN

Math.sign(3);     //  1
Math.sign(-3);    // -1
Math.sign('-3');  // -1
Math.sign(0);     //  0
Math.sign(-0);    // -0
Math.sign(NaN);   // NaN
Math.sign('foo'); // NaN
Math.sign();      // NaN
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
4
var sign = number >> 31 | -number >>> 31;

Superfast if you do not need Infinity and know that the number is an integer, found in openjdk-7 source: java.lang.Integer.signum()

starwed
  • 2,536
  • 2
  • 25
  • 39
Toxiro
  • 528
  • 1
  • 3
  • 12
  • 1
    This fails for small negative fractions like -0.5. (Looks like the source is from an implementation for Integers specifically) – starwed Jun 03 '15 at 18:59
1

I thought I'd add this just for fun:

function sgn(x){
  return 2*(x>0)-1;
}

0 and NaN will return -1
works fine on +/-Infinity

Symbolic
  • 693
  • 5
  • 10
1

A solution that works on all numbers, as well as 0 and -0, as well as Infinity and -Infinity, is:

function sign( number ) {
    return 1 / number > 0 ? 1 : -1;
}

See the question "Are +0 and -0 the same?" for more information.


Warning: None of these answers, including the now standard Math.sign will work on the case 0 vs -0. This may not be an issue for you, but in certain physics implementations it may matter.

Community
  • 1
  • 1
Andy Ray
  • 30,372
  • 14
  • 101
  • 138
0

I just was about to ask the same question, but came to a solution before i was finished writing, saw this Question already existed, but didn't saw this solution.

(n >> 31) + (n > 0)

it seems to be faster by adding a ternary though (n >> 31) + (n>0?1:0)

Moritz Roessler
  • 8,542
  • 26
  • 51
  • Very nice. Your code seems quite a bit faster than (1). (n>0?1:0) is faster beacause of no type cast. The only dissapointing moment is sign(-Infinity) gives 0. Updated tests. – disfated Sep 26 '13 at 18:31
0

Very similar to Martijn's answer is

function sgn(x) {
    isNaN(x) ? NaN : (x === 0 ? x : (x < 0 ? -1 : 1));
}

I find it more readable. Also (or, depending on your point of view, however), it also groks things that can be interpreted as a number; e.g., it returns -1 when presented with '-5'.

equaeghe
  • 1,644
  • 18
  • 37
0

I don't see any practical sence of returning -0 and 0 from Math.sign so my version is:

function sign(x) {
    x = Number(x);
    if (isNaN(x)) {
        return NaN;
    }
    if (x === -Infinity || 1 / x < 0) {
        return -1;
    }
    return 1;
};

sign(100);   //  1
sign(-100);  // -1
sign(0);     //  1
sign(-0);    // -1
Alexander Shutau
  • 2,660
  • 22
  • 32
0

The methods I know of are as follows:

Math.sign(n)

var s = Math.sign(n)

This is the native function, but is slowest of all because of the overhead of a function call. It does however handle 'NaN' where the others below may just assume 0 (i.e. Math.sign('abc') is NaN).

((n>0) - (n<0))

var s = ((n>0) - (n<0));

In this case only the left or right side can be a 1 based on the sign. This results in either 1-0 (1), 0-1 (-1), or 0-0 (0).

The speed of this one seems neck and neck with the next one below in Chrome.

(n>>31)|(!!n)

var s = (n>>31)|(!!n);

Uses the "Sign-propagating right shift". Basically shifting by 31 drops all bits except the sign. If the sign was set, this results in -1, otherwise it is 0. Right of | it tests for positive by converting the value to boolean (0 or 1 [BTW: non-numeric strings, like !!'abc', become 0 in this case, and not NaN]) then uses a bitwise OR operation to combine the bits.

This seems the best average performance across the browsers (best in Chrome and Firefox at least), but not the fastest in ALL of them. For some reason, the ternary operator is faster in IE.

n?n<0?-1:1:0

var s = n?n<0?-1:1:0;

Fastest in IE for some reason.

jsPerf

Tests performed: https://jsperf.com/get-sign-from-value

James Wilkins
  • 6,836
  • 3
  • 48
  • 73
0

My two cents, with a function that returns the same results as Math.sign would do, ie sign(-0) --> -0, sign(-Infinity) --> -Infinity, sign(null) --> 0, sign(undefined) --> NaN, etc.

function sign(x) {
    return +(x > -x) || (x && -1) || +x;
}

Jsperf won't let me create a test or revision, sorry for not being able to provide you with tests (i've given jsbench.github.io a try, but results seem much closer to one another than with Jsperf...)

If someone could please add it to a Jsperf revision, I would be curious to see how it compares to all the previously given solutions...

Thank you!

Jim.

EDIT:

I should have written:

function sign(x) {
    return +(x > -x) || (+x && -1) || +x;
}

((+x && -1) instead of (x && -1)) in order to handle sign('abc') properly (--> NaN)

Jimshell
  • 21
  • 1
  • 3
0

Math.sign is not supported on IE 11. I am combining the best answer with Math.sign answer :

Math.sign = Math.sign || function(number){
    var sign = number ? ( (number <0) ? -1 : 1) : 0;
    return sign;
};

Now, one can use Math.sign directly.

sudip
  • 2,781
  • 1
  • 29
  • 41
  • 1
    You pushed me to update my question. 8 years passed since it was asked. Also updated my jsfiddle to es6 and window.performance api. But I prefer mozilla's version as a polyfill as it matches Math.sign's type coercing. Performance is not much of a concern nowadays. – disfated Oct 26 '19 at 03:57
0

Here is a compact version:

let sign=x=>2*(x>=0)-1
//Tests
console.log(sign(0)); //1
console.log(sign(6)); //1
console.log(sign(Infinity)); //1
console.log(sign(-6)); //-1
console.log(sign(-Infinity)); //-1
console.log(sign("foo")); //-1

If you want to deal with NaN and other edge cases, use this (it is longer though):

let sign=x=>isNaN(x)?NaN:2*(x>=0)-1
//Tests
console.log(sign(0)); //1
console.log(sign(6)); //1
console.log(sign(Infinity)); //1
console.log(sign(-6)); //-1
console.log(sign(-Infinity)); //-1
console.log(sign("foo")); //NaN

If you want sign(0) to return 0 as well:

let sign=x=>isNaN(x)?NaN:(x?2*(x>=0)-1:0)
//Tests
console.log(sign(0)); //0
console.log(sign(6)); //1
console.log(sign(Infinity)); //1
console.log(sign(-6)); //-1
console.log(sign(-Infinity)); //-1
console.log(sign("foo")); //NaN
Nirvana
  • 405
  • 3
  • 15
0

You could bit shift the number and check the Most Significant Bit (MSB). If the MSB is a 1 then the number is negative. If it is 0 then the number is positive (or 0).

Brombomb
  • 6,988
  • 4
  • 38
  • 57
  • @ NullUserException I could still be wrong but from my reading "The operands of all bitwise operators are converted to signed 32-bit integers in big-endian order and in two's complement format." taken from [MDN](https://developer.mozilla.org/en/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers) – Brombomb Oct 02 '11 at 06:46
  • That still seems like an awful lot of work; you still have to convert 1 and 0 to -1 and 1, and 0 also has to be taken care of. If the OP just wanted that, it would be easier just to use `var sign = number < 0 : 1 : 0` – NullUserException Oct 02 '11 at 06:50
  • +1. No need to shift though, you can just do `n & 0x80000000` like a bitmask. As for converting to 0,1,-1: `n && (n & 0x80000000 ? -1 : 1)` – davin Oct 02 '11 at 07:01
  • @davin Are all numbers guaranteed to work with that bitmask? I plugged in `-5e32` and it broke. – NullUserException Oct 02 '11 at 07:05
  • @NullUserExceptionఠ_ఠ, numbers that hold the same sign when applied the standards `ToInt32`. If you read there (section 9.5) there is a modulus that affects the value of the numbers since the range of a 32-bit integer is less than the range of the js Number type. So it won't work for those values, or the infinities. I still like the answer though. – davin Oct 02 '11 at 07:14
  • @NullUserExceptionఠ_ఠ, your value is one of those that breaks not with the bitmask, but in the `ToInt32` phase. `-5e32 << 0 === 0`. – davin Oct 02 '11 at 07:16
  • @davin Your suggestion seems to fail for negative numbers between -1 and 0. – starwed Jun 03 '15 at 18:53