Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Seeing if two numbers have the same sign

by grinder (Bishop)
on Jan 10, 2008 at 14:44 UTC ( [id://661623]=perlquestion: print w/replies, xml ) Need Help??

grinder has asked for the wisdom of the Perl Monks concerning the following question:

I needed to see if two numbers have the same sign (both positive, or both negative), so I wrote the following:

#! /usr/local/bin/perl use strict; use warnings; use Test::More tests => 8; sub same_sign { my $x = shift; my $y = shift; return if $x > 0 and $y < 0 or $x < 0 and $y > 0; return 1; } ok(same_sign( 2, 4), '+ve +ve'); ok(same_sign(-2,-4), '-ve -ve'); ok(!same_sign( 2,-4), '+ve -ve'); ok(!same_sign(-2, 4), '-ve +ve'); ok(same_sign( 2, 0), '+ve 0'); ok(same_sign( 0, 4), '0 +ve'); ok(same_sign( -2, 0), '-ve 0'); ok(same_sign( 0, -4), '0 -ve');

I may be remembering things incorrectly, but I'm sure I learnt some simple trick at school involving abs or the spaceship operator, but I can't remember it. Is there another way do this? Is the code I've written clear, or could it be improved?

I'm not interested in the theological debates concerning the sign of zero, though :)

• another intruder with the mooring in the heart of the Perl

Replies are listed 'Best First'.
Re: Seeing if two numbers have the same sign
by Corion (Patriarch) on Jan 10, 2008 at 14:50 UTC

    How about exploiting that two numbers have the same sign when their product is positive?

    sub same_sign { $_[0]*$_[1] > 0 };

    The above version will treat 0 as a singularity which never has the same sign with anything, not even with itself. If you don't want that, you can make 0 have the same sign as a positive and a negative number:

    sub same_sign { $_[0]*$_[1] >= 0 };

    ... but you can't say "0 is always positive" or "0 always is negative" with my method.

    Update: After thinking about it a bit, I think the <=>-trick is the following:

    sub same_sign { ($_[0] <=> 0) == ($_[1] <=> 0) };

    because shift <=> 0 is the sign function. It has different properties again - 0 has no sign, and compares as the same sign as 0, but it's different from any positive and any negative number.

Re: Seeing if two numbers have the same sign
by f00li5h (Chaplain) on Jan 10, 2008 at 14:56 UTC

    sub same_sign{ my($left, $right) =@_; (0 <=> $left) == (0<=> $right); }

    @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;

      What is the goal here?

      Using <=> is clever, I think. And by that I mean that it invokes a less common set of symbols to come to a result in a surprising and elegant way.

      I'm not sure that is good, though. Assuming that if either variable is zero you should return 0, my first thought was

      if ($left > 0) { if ($right > 0) { # both pos return 1 } } elsif ($left < 0) { if ($right < 0) { # both neg return 1 } } return 0;
      That is, anchor one, see if the other is the same, and if so return 1; otherwise, fall through and return 0.

      I think this is bad because its a whole bunch of lines. On the other hand, I think its good because its dead obvious, and probably pretty fast (fewer compares and jumps than even the double '<=>' version).

      ymmv. "What is good?", asked jesting Pilate, not waiting for an answer.

      --woody

        Using the shuttles I'm basiclaly asking "Do these 2 numbers compare to zero the same way"

        Personally i'm a fan of the $left * $right > 0 solution, it has a nice mathy feel. although may have issues for massive numbers etc (Corion++ on that count)

        @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;

      Nice, f00li5h++
      sub same_sign { (0 <=> $_[0]) == (0 <=> $_[1]) || (! $_[0] || ! $_[1]); }

      to check for either operand being somewhat false ;-)

      --shmem

      _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                    /\_¯/(q    /
      ----------------------------  \__(m.====·.(_("always off the crowd"))."·
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Seeing if two numbers have the same sign
by demerphq (Chancellor) on Jan 10, 2008 at 18:55 UTC

    You are doing more work than you need to. Remember that mathematical comparisons return booleans, and booleans can be directly compared.

    if ($x<0 == $y<0) { print "$x and $y have the same sign"; }

    (($x ^ $y) < 0) would probably also work assuming they are integers. I think the trick with abs or the comparison operator would probably actually be less efficient than doing a comparison. Using multiplication is going to be slow as well.

    BTW the sign of zero isnt a theological debate, it is an implementation detail of the machine you are working on and the data types you are using. :-)

    ---
    $world=~s/war/peace/g

      I don't understand your (($x^$y)<0).

      Using

      sub andNeg { return (($_[0]^$_[1]) < 0); }
      and test harness
      print "<table border>\n<tr><td>", join("</td><td>", "x", "y", "andNeg( +)", "spaceship()", "mult()", "anchor()"), "</td></tr>\n"; for (0..3) { my $x = 1 - 2 * ($_ % 2); my $y = 1 - 2 * int($_ / 2); print "<tr><td>", join("</td><td>", $x, $y, andNeg($x, $y), spaceshi +p($x, $y), mult($x, $y), anchor($x, $y)), "</td></tr>\n"; } print "</table>\n";
      I get
      xyxorNeg()spaceship()mult()anchor()
      11111
      -110
      1-10
      -1-1111
      As for spaceship, multiplication, and logic, implemented as
      sub spaceship { (0 <=> $_[0]) == (0<=> $_[1]); } sub mult { $_[0]*$_[1] > 0; } sub anchor { if ($_[0] > 0) { if ($_[1] > 0) { # both pos return 1 } } elsif ($_[0] < 0) { if ($_[1] < 0) { # both neg return 1 } } return 0; }
      I get
      unit squarespaceshipmultiplylogic
      spaceship--------worse 22.28%worse 10.40%
      multiply better 22.28%--------better 13.26%
      logic better 10.40%worse 13.26%--------
      and
      signed intspaceshipmultiplylogic
      spaceship--------worse 21.43%worse 7.65%
      multiply better 21.43%--------better 14.92%
      logic better 7.65%worse 14.92%--------
      that is, if you take points (x,y) uniformly chosen from the unit disk [-1,1]x[-1,1], then spaceship is 22% worse than testing multiplication, multiplication is 13% better than using logic, etc; and if you take them as random unsigned ints, multiplication is (surprisingly!) even better.

      full test program (don't use this use Benchmark; instead)

        Hmm. I didnt actually test it and had assumed that the result of binary xor was going to be signed, but of course its unsigned, hence the xor ideas doesnt work as written. My bad. Sorry. Id just use the $x<0 == $y<0 approach anyway. :-)

        ---
        $world=~s/war/peace/g

      Neat (wish I'd thought of it) but it implicitly assumes that zero is a positive number, given 0 and -1 the test returns 0, given 0 and +1 it returns 1. If the test was ( $x<=0 == $y<=0 ) then zero is treated as both positive and negative and the two previous examples return 1. Which is consistent but might not be what you want.

        Maybe I'm just old-fashioned (primitive?), but my understanding of the term "same sign", at least as it relates to programming, has always been: when the sign bit of two values is the same, those values have "the same sign". In that interpretation, zero is always included among the set of "non-negative" values: it is the same sign as "1", and not the same sign as "-1".
      I like your method better than mine (snarls enviously).

      Mathematically, the sign of zero is undefined. Just as in football, you can't say which side of center the center lines up on.

Re: Seeing if two numbers have the same sign
by girarde (Hermit) on Jan 10, 2008 at 18:27 UTC
    This will work:

    my $samesign, $first, $second; if (($first * $second) > 0) { $samesign = 'true'; } elsif (($first * $second) < 0) { { $samesign = 'false'; } else { $samesign = undef; }
    The else condition means that one of the numbers is zero, and sifgn doesn't mean anything then.
Re: Seeing if two numbers have the same sign
by poolpi (Hermit) on Jan 10, 2008 at 21:39 UTC
    With re, just for fun ;)

    sub same_sign { my $i=0; /-(?{$i=$i+1})/ for @_; $i=~/0|2/; }

    PooLpi
      I was thinking about that! Convert to a string, and look to see if the first char is a "-" to determine sign. I don't think your routine works, though.
      sub re { my $i=0; /-(?{$i=$i+1})/ for @_; $i=~/0|2/; }
      given the same test cases as before,
      xyxorNeg()spaceship()mult()anchor()re()
      111111
      -1101
      1-101
      -1-11111
      And actually, its only twice as slow...
      unit squarespaceshipmultiplylogicre
      spaceship--------worse 21.74%worse 12.21%better 86.26%
      multiplybetter 21.74%--------better 10.86%better 89.25%
      logicbetter 12.21%worse 10.86%--------better 87.94%
      reg expworse 86.26%worse 89.25%worse 87.94%--------
      Works under XP with Cygwin 1.5.24-2 but not under Linux 2.6.18-4-686 !

      This one works under Linux 2.6.18-4-686 with Perl v5.8.8 :
      #!/usr/bin/perl use strict; use warnings; sub same_sign { my $i=0; printf( "%2d %2d => ", $_[0], $_[1] ); s/-/$i=$i+1;/e for @_; $i!~/1/; } my $val = [ [ 1, 2 ], [ -1, 2 ], [ 1, -2 ], [ -1, -2 ] ]; for ( 0 .. $#{$val} ) { my ( $x, $y ) = ( $val->[$_][0], $val->[$_][1] ); print same_sign( $x, $y ) ? '' : 'not ', "same sign\n"; }
      Output:
      1 2 => same sign -1 2 => not same sign 1 -2 => not same sign -1 -2 => same sign


      PooLpi
Re: Seeing if two numbers have the same sign
by syphilis (Archbishop) on Jan 11, 2008 at 10:54 UTC
    I learnt some simple trick at school involving abs

    Possibly something like:
    sub same_sign { abs($_[0] + $_[1]) == abs($_[0]) + abs($_[1]); }
    Cheers,
    Rob
Re: Seeing if two numbers have the same sign ($x / abs $x)
by lodin (Hermit) on Jan 11, 2008 at 11:48 UTC

    I sometimes use $x / abs $x to get the sign of $x (usually to get a coefficient to change the sign of something else). Unlike all other solutions above it has the added benefit of implicitly blowing up when $x is zero, which is a good thing for me as $x shouldn't be zero when I use this. So a (probably expensive) way of doing you subroutine is

    sub same_sign { $_[0] / abs $_[0] == $_[1] / abs $_[1] }

    Since the sign of zero usually is undefined, same_sign(0, 0) should usually blow up, even though $_[0] == $_[1]. Things with no sign can't have the same sign.

    Of course, it all depends on what you need the signs for.

    lodin

      <obsMathRef>
      lodin's observation and syphilis' cantrip come from the same underlying mathematical principles.

      Triangle equality:
      for vectors a, b,

      ||a+b|| <= ||a|| + ||b||, with equality iff a is a positive scalar multiple of b

      (Note here || is "norm", not or.)

      Angle between two vectors:
      The angle theta between two vectors a, b satisfies

      cos theta = (a dot b) / (||a|| ||b||)

      (That the second theorem implies the first is left as an exercise for the reader. :-)

      For one dimensional vectors ("numbers") the norm is the absolute value, and there are only two "directions" -- out along the positive axis and out along the negative axis. So syphilis is applying the triangle equality, while lodin is computing the cosine directly (note cos 0 = 1 -> "same sign" while cos 180 degrees = -1 -> "opposite sign").
      </obsMathRef>

      We now return to our regular interpretation of line noise as script.

      --woody

        lodin is computing the cosine directly (note cos 0 = 1 -> "same sign" while cos 180 degrees = -1 -> "opposite sign").

        Maybe this is what you mean, but I don't actually calculate the angle (or cosine) between $_[0] and $_[1]. I calculate the respective angles of $_[0] and $_[1] against the positive axis, and compare them.

        Equivalently, you could also view it as I'm taking the norm of the two 1D vectors $_[0] and $_[1], which gives me two unit vectors pointing in the direction of $_[0] and $_[1], which I then test for equality. If they're equal, they point in the same direction, and in 1D that means they have the same sign.

        lodin

Re: Seeing if two numbers have the same sign
by graff (Chancellor) on Jan 12, 2008 at 18:37 UTC
    ... return if $x > 0 and $y < 0 or $x < 0 and $y > 0; ...
    Is the code I've written clear, or could it be improved?

    IMHO, that line of code is not clear, and cries out for improvement -- at the very least, there should be parens to eliminate any hint of ambiguity or confusion in the sequence of evaluation:

    return if ($x > 0 and $y < 0) or ($x < 0 and $y > 0);
    There could be other possible ways to evaluate the four conditions when parens are not provided; some would be nonsensical, and others would simply give the wrong result. Personally, my memorization of precedence rules in perl is not so detailed that I can reliably parse that line in my head the same way perl would. (And the habit of omitting parens is not readily portable across languages.)

    In any case, as others point out, there are better ways to improve it.

    I'm not interested in the theological debates concerning the sign of zero, though :)

    (Did you add that comment after seeing some of the replies, or have people been talking about this despite your clear statement at the beginning not to do that? If the latter, how rude of them! But since your code seems to make an assertion about the sign of zero that some would disagree with, you have to expect some reactions.)

    I'm actually kind of surprised at the divergence of views on this point among some of the monks. Every numeric value (even zero) has a sign bit, which is either "on" or "off", and for zero, it happens to be "off". Since your approach would return "true" given "0" and "-1", you are actually testing for something other than "sign". (I'm not sure what this "other thing" would be called...)

Re: Seeing if two numbers have the same sign
by Anonymous Monk on Jan 14, 2008 at 03:16 UTC
    ($i<0)==($j<0)
Re: Seeing if two numbers have the same sign
by Anonymous Monk on Jan 11, 2008 at 16:59 UTC

    Don't code “clever!” The simplest and most expressive code is always best:

    sub same_sign { my ($x,$y) = @_; if ( undef($x) or undef($y)) { return 0; # "undef" is never same-sign } if ( ( ($x >= 0) and ($y >= 0) ) or ( ($x < 0) and ($y < 0) ) ) { return 1; } else { return 0; } }

    Is this (caution: extemporaneous Perl...) module terse? No. Could it have been written with fewer characters? Yes. But neither of those considerations are “the point.” What matters to me is that it is obvious what this code is actually supposed to do. Everything about it, including the white space, is designed to encourage readability among humans. The Perl compiler can take care of itself.

      I suggest you test your code before posting:

      use strict; sub same_sign { my ($x,$y) = @_; if ( undef($x) or undef($y)) { return 0; # "undef" is never same-sign } if ( ( ($x >= 0) and ($y >= 0) ) or ( ($x < 0) and ($y < 0) ) ) { return 1; } else { return 0; } } $\ = "\n"; print same_sign(1,1); print same_sign(0,0); print same_sign(undef,undef); print same_sign(-1,-1); print same_sign(-2,-2); print same_sign(2,-2); __END__ 1 1 1 1 1 1

      Your subroutine seems to suggest that all values passed in always have the same value sign...

      Update: Correction thanks to lodin

      You probably meant !defined() where you put undef().

      And personally i think your code is much harder to understand than what i posted. Longer is very often less comprehensible than shorter for the simple fact that most people can only operate on a small number of items at a time. (8 or so).

      ---
      $world=~s/war/peace/g

        You probably meant 5..9 where you put (8 or so). :-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://661623]
Approved by Corion
Front-paged by citromatik
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (4)
As of 2024-03-19 05:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found