41

Is there a (simple) way to get the "sign" of a number (integer) in PHP comparable to gmp_signDocs:

  • -1 negative
  • 0 zero
  • 1 positive

I remember there is some sort of compare function that can do this but I'm not able to find it at the moment.

I quickly compiled this (Demo) which does the job, but maybe there is something more nifty (like a single function call?), I would like to map the result onto an array:

$numbers = array(-100, 0, 100);

foreach($numbers as $number)
{
   echo $number, ': ', $number ? abs($number) / $number : 0, "\n";
}

(this code might run into floating point precision problems probably)

Related: Request #19621 Math needs a "sign()" function

hakre
  • 193,403
  • 52
  • 435
  • 836

9 Answers9

72

Here's a cool one-liner that will do it for you efficiently and reliably:

function sign($n) {
    return ($n > 0) - ($n < 0);
}
Milosz
  • 2,924
  • 3
  • 22
  • 24
  • 11
    Well, you won't run into integer overflow issues or float precision issues since no arithmetic is performed on `$n`. Moreover, IEEE floats do follow the law of excluded middle (`¬(A > B) ⇒ A ≤ B`, `¬(A < B) ⇒ A ≥ B`), so you won't get a nonzero number satisfying both conditions and yielding an incorrect sign of `0`. Finally, both `-0` and `0` are treated as equal to zero by IEEE specs, so will both return false under both conditions, yielding a sign of `0`. It's going to work for all numeric inputs. Feeding `NAN` into the function yields `1 - 1 = 0` which is as good an answer as any I suppose. – Milosz Apr 21 '14 at 20:16
45

In PHP 7 you should use the combined comparison operator (<=>):

$sign = $i <=> 0;
kdojeteri
  • 755
  • 6
  • 17
  • 1
    This is good, I love it. The spaceship operator. But it will break servers that have not yet been upgraded to PHP 7.0. Sadly those still exist. – asiby Jun 27 '19 at 16:48
  • 2
    AFAIK, in PHP result of `<=>` isn't guaranteed to be `+1`, `0` or `-1`, it can be any positive integer instead of `+1` and any negative integer instead if `-1` (see discussion in comments [here](/a/31298778)). So, the TS may need some function to process the result of `<=>`. – Sasha Jun 05 '20 at 17:59
18

A variant to the above in my question I tested and which works as well and has not the floating point problem:

min(1, max(-1, $number))

Edit: The code above has a flaw for float numbers (question was about integer numbers) in the range greater than -1 and smaller than 1 which can be fixed with the following shorty:

min(1, max(-1, $number == 0 ? 0 : $number * INF))

That one still has a flaw for the float NAN making it return -1 always. That might not be correct. Instead one might want to return 0 as well:

min(1, max(-1, (is_nan($number) or $number == 0) ? 0 : $number * INF))
hakre
  • 193,403
  • 52
  • 435
  • 836
9

You can nest ternary operators:

echo $number, ': ',  ($number >= 0 ? ($number == 0 ? 0 : 1) : -1 )

This has no problem with floating point precision and avoids an floating point division.

rocksportrocker
  • 7,251
  • 2
  • 31
  • 48
  • @Gordon Could you provide a link for that? – flu Sep 24 '14 at 10:08
  • 1
    It's here: http://php.net/manual/en/language.operators.comparison.php and the reason is that it's not obvious what (true?'true':false?'t':'f') will return (it's 't', not 'true'). rocksportrocker used parentheses to ensure the order of evaluation, and that's okay. – Rolf Aug 16 '15 at 16:56
6

What's wrong with this form?

if ( $num < 0 )
{
  //negative
}
else if ( $num == 0 )
{
  //zero
}
else
{
  //positive
}

or ternary:

$sign = $num < 0 ? -1 : ( $num > 0 ? 1 : 0 );

Not sure of the performance of abs vs value comparison, but you could use:

$sign = $num ? $num / abs($num) : 0;

and you could turn any of them into a function:

function valueSign($num)
{
  return $sign = $num < 0 ? -1 : ( $num > 0 ? 1 : 0 );
  //or
  return $sign = $num ? $num / abs($num) : 0;
}

I suppose you could be talking about gmp_cmp, which you could call as gmp_cmp( $num, 0 );

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
4

I think that gmp_sign is not very efficient because it expects a GMP or string. ($n ? abs($n)/$n : 0) is mathematically correct, but the division costs time. The min/max solutions seem to get unnecessary complex for floats.

($n > 0) - ($n < 0) always does 2 tests and one subtraction ($n < 0 ? -1 : ($n > 0 ? 1 : 0) does one or two tests and no arithmetic, it should be most efficient. But I don't believe that the difference will be relevant for most use cases.

Rolf
  • 730
  • 6
  • 14
4

I know this is late but what about simply dividing the number by the abs() of itself?

Something like:

function sign($n) {
    return $n/(abs($n));
}

Put whatever error handling you want for div by zero.

andrewsi
  • 10,807
  • 132
  • 35
  • 51
sansig
  • 81
  • 7
  • 1
    good point, perhaps a bit rough at the edges (not saying other answers are complete, my old one also has rough edges IIRC), it's not only division by zero but also INF which needs handling for the division operator. – hakre Feb 21 '16 at 23:41
2

Use strcmpDocs:

echo $number, ': ', strcmp($number, 0), "\n";
hakre
  • 193,403
  • 52
  • 435
  • 836
Toto
  • 89,455
  • 62
  • 89
  • 125
  • Does this work for numbers (as strings)? I think that's the function I was thinking about, but I'm unsure if it's really doing the job. – hakre Sep 26 '11 at 14:31
  • Yes it does. Have a try with it. – Toto Sep 26 '11 at 14:33
  • Cool! The docs is a little unprecise if it's always `-1, 0 or 1`, however, I will try it in the code. Thanks! – hakre Sep 26 '11 at 14:40
  • 1
    this is elegant, but not very efficient. Converting an int to a string needs much longer than comparisons to 0. – rocksportrocker Sep 26 '11 at 19:04
  • 1
    I now have tested this for some hours. If the `$number` actually is a string (and representing zero, like `"n/a"`), this won't work (min(max) works here). Just noting, it's a side-case, just leaving this for the note. It generally worked pretty well, *but not* for string variables representing the numerical value `0` as we know it in PHP. @rocksportrocker: There are not really actual types like string or intever in PHP, so the conversion argument seems bogus in my eyes. Would be micro-optimizing for nothing anyway to that closely watch at it. – hakre Sep 27 '11 at 10:00
  • @hakre: Thanks for the note. That's true when comparing string ( != '' ) and number. – Toto Sep 27 '11 at 11:16
  • ```strcmp("110","12")``` say ```12``` biger than ```110``` (this function compare strings char by char) – Yukulélé Apr 04 '13 at 15:18
1

Here's one without loop:

function sign($number){
    echo $number, ': ', $number ? abs($number) / $number : 0, "\n";
}

$numbers = array(-100, 0, 100);

array_walk($numbers, 'sign');
Yeroon
  • 3,223
  • 2
  • 22
  • 29