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
Re: Seeing if two numbers have the same sign
by Corion (Pope) on Jan 10, 2008 at 14:50 UTC

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.  [reply] [d/l] [select] 
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;
 [reply] [d/l] [select] 

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
 [reply] [d/l] 

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;
 [reply] [d/l] [select] 

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}
 [reply] [d/l] 
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
 [reply] [d/l] 

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
x  y  xorNeg()  spaceship()  mult()  anchor() 
1  1   1  1  1 
1  1     0 
1  1     0 
1  1   1  1  1 
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 square  spaceship  multiply  logic 
spaceship    worse 22.28%  worse 10.40% 
multiply  better 22.28%    better 13.26% 
logic  better 10.40%  worse 13.26%   
and
signed int  spaceship  multiply  logic 
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)
 [reply] [d/l] [select] 

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
 [reply] 

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.
 [reply] [d/l] 

Maybe I'm just oldfashioned (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 "nonnegative" values: it is the same sign as "1", and not the same sign as "1".
 [reply] 

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

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.  [reply] [d/l] 
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=~/02/;
}
PooLpi  [reply] 

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=~/02/;
}
given the same test cases as before,
x  y  xorNeg()  spaceship()  mult()  anchor()  re() 
1  1   1  1  1  1 
1  1     0  1 
1  1     0  1 
1  1   1  1  1  1 
And actually, its only twice as slow...
unit square  spaceship  multiply  logic  re 
spaceship    worse 21.74%  worse 12.21%  better 86.26% 
multiply  better 21.74%    better 10.86%  better 89.25% 
logic  better 12.21%  worse 10.86%    better 87.94% 
reg exp  worse 86.26%  worse 89.25%  worse 87.94%   
 [reply] [d/l] 

Works under XP with Cygwin 1.5.242 but not under Linux 2.6.184686 !
This one works under Linux 2.6.184686 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
 [reply] [d/l] [select] 
Re: Seeing if two numbers have the same sign
by syphilis (Chancellor) 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  [reply] [d/l] 
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  [reply] [d/l] [select] 

<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
 [reply] 

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
 [reply] [d/l] [select] 

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...)  [reply] [d/l] [select] 
Re: Seeing if two numbers have the same sign
by Anonymous Monk on Jan 14, 2008 at 03:16 UTC

 [reply] [d/l] 
Re: Seeing if two numbers have the same sign
by Anonymous Monk on Jan 11, 2008 at 16:59 UTC

sub same_sign {
my ($x,$y) = @_;
if ( undef($x) or undef($y)) {
return 0; # "undef" is never samesign
}
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.
 [reply] [d/l] 

use strict;
sub same_sign {
my ($x,$y) = @_;
if ( undef($x) or undef($y)) {
return 0; # "undef" is never samesign
}
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  [reply] [d/l] 

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
 [reply] 

You probably meant 5..9
where you put (8 or so). :)
 [reply] 


