Why doesn't Python have a sign function?
Asked Answered
M

12

368

I can't understand why Python doesn't have a sign function. It has an abs builtin (which I consider sign's sister), but no sign.

In python 2.6 there is even a copysign function (in math), but no sign. Why bother to write a copysign(x,y) when you could just write a sign and then get the copysign directly from abs(x) * sign(y)? The latter would be much more clear: x with the sign of y, whereas with copysign you have to remember if it's x with the sign of y or y with the sign of x!

Obviously sign(x) does not provide anything more than cmp(x,0), but it would be much more readable that this too (and for a greatly readable language like python, this would have been a big plus).

If I were a python designer, I would been the other way arond: no cmp builtin, but a sign. When you need cmp(x,y), you could just do a sign(x-y) (or, even better for non-numerical stuff, just a x>y - of course this should have required sorted accepting a boolean instead of an integer comparator). This would also be more clear: positive when x>y (whereas with cmp you have to remember the convention positive when the first is bigger, but it could be the other way around). Of course cmp makes sense in its own for other reasons (e.g. when sorting non-numerical things, or if you want the sort to be stable, which is not possible using with simply a boolean)

So, the question is: why did the Python designer(s) decide to leave the sign function out of the language? Why the heck bother with copysign and not its parent sign?

Am I missing something?

EDIT - after Peter Hansen comment. Fair enough that you didn't use it, but you didn't say what you use python for. In 7 years that I use python, I needed it countless times, and the last is the straw that broke the camel's back!

Yes, you can pass cmp around, but 90% of the times that I needed to pass it was in an idiom like lambda x,y: cmp(score(x),score(y)) that would have worked with sign just fine.

Finally, I hope you agree that sign would be more useful than copysign, so even if I bought your view, why bother about defining that in math, instead of sign? How can copysign be so much useful than sign?

Madid answered 31/12, 2009 at 16:56 Comment(9)
"Am I missing something?" If nothing else, the fact that using sign(x-y) requires an expression be evaluated, which means you can't pass it in as a callback unless you wrap it in a "partial" first. With cmp() as defined, you just pass a reference to cmp around. Also "incredibly useful"? I doubt it. I've had a need for that once in 10 years of writing Python. If it were really so useful, I think it would be there...Omalley
@dmazzoni: wouldn't this argument work for all the questions on this site? just close stackoverflow and ask every question to the relevant topic dev or user mailing list!Madid
@davide: First I agree, a signum function is common and useful. Secondly, I agree with dmazzoni in that your question is better asked on python-dev. It is one thing to ask how to use a list comprehension to do xyz: we can do a good job answering that here. But asking why Guido Van Rossum left out signum is tough for anyone here to answer.Foray
The proper place for a question is any place where it's likely to be answered. Thus, stackoverflow is a proper place.Causality
@Davide, I've used Python for just about every form of software there is, literally. I just don't work exclusively in an area where sign() would be so useful. You appear to; the developers clearly do not. As has been noted, if you really want the answer, go ask where the developers themselves hang out, and you'll get an earful.Omalley
-1: @Davide: "Why" and "why not" questions generally cannot be answered here. Since most of the principals of Python development don't answer questions here, you're rarely (if ever) going to get an answer to a "why" or "why not" question. Further, you don't have a problem to solve. You sound like you have a rant. If you have a problem ("How do I work around the lack of sign in this example...") that's sensible. "Why not" isn't sensible for this venue.Tartan
Since you're asking why someone designed something a particular way, why don't you ask the designer directly? Trying to get others to speculate isn't particularly useful.Fowliang
The question might be a little emotional, but I don't think it's a bad question. I'm sure lots of people have looked for a built-in sign function, so it can be curious why there isn't one.Unsteady
This is a perfectly objective question: “Why” Python lacks any given feature is a legitimate query about the history of language design that can be answered by linking to the appropriate discussion from python-dev or other forums (sometimes blog posts) where the Python core developers happen to hash a topic out. Having tried to Google for bits of history in python-dev myself before, I can understand why a newcomer to the language might hit a dead end and come ask here in the hopes of a more experienced Python person answering!Hazeghi
U
341

EDIT:

Indeed there was a patch which included sign() in math, but it wasn't accepted, because they didn't agree on what it should return in all the edge cases (+/-0, +/-nan, etc)

So they decided to implement only copysign, which (although more verbose) can be used to delegate to the end user the desired behavior for edge cases - which sometimes might require the call to cmp(x,0).


I don't know why it's not a built-in, but I have some thoughts.

copysign(x,y):
Return x with the sign of y.

Most importantly, copysign is a superset of sign! Calling copysign with x=1 is the same as a sign function. So you could just use copysign and forget about it.

>>> math.copysign(1, -4)
-1.0
>>> math.copysign(1, 3)
1.0

If you get sick of passing two whole arguments, you can implement sign this way, and it will still be compatible with the IEEE stuff mentioned by others:

>>> sign = functools.partial(math.copysign, 1) # either of these
>>> sign = lambda x: math.copysign(1, x) # two will work
>>> sign(-4)
-1.0
>>> sign(3)
1.0
>>> sign(0)
1.0
>>> sign(-0.0)
-1.0
>>> sign(float('nan'))
-1.0

Secondly, usually when you want the sign of something, you just end up multiplying it with another value. And of course that's basically what copysign does.

So, instead of:

s = sign(a)
b = b * s

You can just do:

b = copysign(b, a)

And yes, I'm surprised you've been using Python for 7 years and think cmp could be so easily removed and replaced by sign! Have you never implemented a class with a __cmp__ method? Have you never called cmp and specified a custom comparator function?

In summary, I've found myself wanting a sign function too, but copysign with the first argument being 1 will work just fine. I disagree that sign would be more useful than copysign, as I've shown that it's merely a subset of the same functionality.

Unsteady answered 31/12, 2009 at 19:25 Comment(10)
Using [int(copysign(1, zero)) for zero in (0, 0.0, -0.0)] gives [1, 1, -1]. That should have been [0, 0, 0] according to en.wikipedia.org/wiki/Sign_functionIllogicality
You have the order wrong. [int(copysign(zero, 1)) for zero in (0, 0.0, -0.0)] gives the [0,0,0] you're looking for.Grafting
@Andrew - @user238424's calling order is correct. copysign(a,b) returns a with the sign of b - b is the varying input, a is the value to normalize to with b's sign. In this case, the commenter is illustrating that copysign(1,x) as a replacement for sign(x) fails, since it returns 1 for x=0, whereas sign(0) would evaluate to 0.Vaginismus
Floats hold "sign" separate from "value"; -0.0 is a negative number, even if that seems an implementation error. Simply using cmp() will give the desired results, probably for nearly every case anyone would care about: [cmp(zero, 0) for zero in (0, 0.0, -0.0, -4, 5)] ==> [0, 0, 0, -1, 1].Saturnian
s = sign(a) b = b * s is not equivalent to b = copysign(b, a)! It does not consider the sign of b. E.g. if a=b=-1 the first code will return 1 while the second returns -1Vibration
Seeing the false sign() replacement definition, the false equivalent for multiplication with sign(a), the false explanation for the motivation of copysign, and the correct replacement "cmp(x, 0)" being already mentioned in the question - there is not much info and it is unclear to me why this is the "accepted" answer with so many votes.?Aldo
I've never seen a sign function where sign(0) == 1. That doesn't seem like it follows any convention I've heard of.Camillacamille
@Camillacamille in analysis of data it might occur - "reaching zero from the positive or negative side". - Though for those we have complete different library instead of math.Mechanotherapy
sign(0) == 1 is totally wrongPickaninny
How do you make sign(0) == 0 ?Pickaninny
G
81

copysign() is defined by IEEE 754, and part of the C99 specification. That's why it's in Python. The function cannot be implemented in full by abs(x) * sign(y) because of how it's supposed to handle NaN values.

>>> import math
>>> math.copysign(1, float("nan"))
1.0
>>> math.copysign(1, float("-nan"))
-1.0
>>> math.copysign(float("nan"), 1)
nan
>>> math.copysign(float("nan"), -1)
nan
>>> float("nan") * -1
nan
>>> float("nan") * 1
nan
>>>

That makes copysign() a more useful function than sign().

As to specific reasons why IEEE's signbit(x) is not available in standard Python, I don't know. I can make assumptions, but it would be guessing.

The math module itself uses copysign(1, x) as a way to check if x is negative or non-negative. For most cases dealing with mathematical functions that seems more useful than having a sign(x) which returns 1, 0, or -1 because there's one less case to consider. For example, the following is from Python's math module:

static double
m_atan2(double y, double x)
{
    if (Py_IS_NAN(x) || Py_IS_NAN(y))
        return Py_NAN;
    if (Py_IS_INFINITY(y)) {
        if (Py_IS_INFINITY(x)) {
            if (copysign(1., x) == 1.)
                /* atan2(+-inf, +inf) == +-pi/4 */
                return copysign(0.25*Py_MATH_PI, y);
            else
                /* atan2(+-inf, -inf) == +-pi*3/4 */
                return copysign(0.75*Py_MATH_PI, y);
        }
        /* atan2(+-inf, x) == +-pi/2 for finite x */
        return copysign(0.5*Py_MATH_PI, y);

There you can clearly see that copysign() is a more effective function than a three-valued sign() function.

You wrote:

If I were a python designer, I would been the other way around: no cmp builtin, but a sign.

That means you don't know that cmp() is used for things besides numbers. cmp("This", "That") cannot be implemented with a sign() function.

Edit to collate my additional answers elsewhere:

You base your justifications on how abs() and sign() are often seen together. As the C standard library does not contain a sign(x) function of any sort, I don't know how you justify your views. There's an abs(int) and fabs(double) and fabsf(float) and fabsl(long) but no mention of sign(). There is copysign() and signbit() but those only apply to IEEE 754 numbers.

With complex numbers, what would sign(-3+4j) return in Python, were it to be implemented? abs(-3+4j) return 5.0. That's a clear example of how abs() can be used in places where sign() makes no sense.

Suppose sign(x) were added to Python, as a complement to abs(x). If x is an instance of a user-defined class which implements the __abs__(self) method then abs(x) will call x.__abs__(). In order to work correctly, to handle abs(x) in the same way then Python will have to gain a __sign__(x) slot.

This is excessive for a relatively unneeded function. Besides, why should sign(x) exist and nonnegative(x) and nonpositive(x) not exist? My snippet from Python's math module implementation shows how copysign(x, y) can be used to implement nonnegative(), which a simple sign(x) cannot do.

Python should have better support for IEEE 754/C99 math functions. That would add a signbit(x) function, which would do what you want in the case of floats. It would not work for integers or complex numbers, much less strings, and it wouldn't have the name you are looking for.

You ask "why", and the answer is "sign(x) isn't useful." You assert that it is useful. Yet your comments show that you do not know enough to be able to make that assertion, which means you would have to show convincing evidence of its need. Saying that NumPy implements it is not convincing enough. You would need to show cases of how existing code would be improved with a sign() function.

And that it outside the scope of StackOverflow. Take it instead to one of the Python lists.

Grafting answered 31/12, 2009 at 19:12 Comment(5)
Well, I don't if that will make you happy, but Python 3 has neither cmp() nor sign() :-)Glialentn
writing a good sign() function that would work properly with IEEE 754 is not trivial. This would be a good point to include it in the language, rather than leaving it out, even though I didn't elaborate this point in the questionMadid
Your comment about how "if you want the sort to be stable" means you also don't know how stable sort works. Your statement that copysign and sign are equivalent show that you didn't know much about IEEE 754 math before this post. Should Python implement all of the 754 math functions in core? What should it do for non-C99 compilers? Non-754 platforms? "isnonnegative" and "isnonpositive" are also useful functions. Should Python also include those? abs(x) defers to x.__abs__(), so should sign(x) defer to x.__sign__() ? There's little demand or need for it, so why should it be stuck into core?Grafting
math.copysign(1, float("-nan")) returns 1.0 instead of -1.0 when I try it in 2.7Lutherlutheran
The sign or signum function is a standard mathematical function defined as sign(z) = z/|z|. Its meaning for complex numbers is well-defined; just use that same definition with the complex version of division and absolute value. The result is a complex number whose magnitude is 1; e.g. sign(-3+4j) is -0.6+0.8j. I don't see how the function's absence in C is relevant to its absence from Python. It exists in Microsoft BASIC; does that matter?Unjust
L
65

Another one liner for sign()

sign = lambda x: (1, -1)[x<0]

If you want it to return 0 for x = 0:

sign = lambda x: x and (1, -1)[x<0]
Lutherlutheran answered 24/5, 2013 at 1:37 Comment(5)
why? The question itself acknowledges that cmp(x, 0) is equivalent to sign, and lambda x: cmp(x, 0) is more readable than what you suggest.Megalomania
Indeed, I was wrong. I had assumed that 'cmp' was specified to return -1,0,+1, but I see that the spec does not guarantee that.Megalomania
Is there any advantage to using lists instead of -1 if x < 0 else 1?Wingard
sign = lambda x: -1 if x < 0 else 1 is 15% faster. Same with sign = lambda x: x and (-1 if x < 0 else 1).Wingard
I've checked various examples. If you consider suggestion from @MateenUlhaq, it is fastest sign function with -1, 0, 1 returns.Xanthine
V
53

Since cmp has been removed, you can get the same functionality with

def cmp(a, b):
    return (a > b) - (a < b)

def sign(a):
    return (a > 0) - (a < 0)

It works for float, int and even Fraction. In the case of float, notice sign(float("nan")) is zero.

Python doesn't require that comparisons return a boolean, and so coercing the comparisons to bool() protects against allowable, but uncommon implementation:

def sign(a):
    return bool(a > 0) - bool(a < 0)
Viticulture answered 4/6, 2014 at 10:48 Comment(1)
For the record, this implementation returns -1 if a < 0, 0 if a = 0, and 1 if x > 0 as per the definition on Wikipedia.Tusche
C
27

The definition on Wikipedia reads:

sign definition

Hence, in order to be compliant with the definition:

sign = lambda x: -1 if x < 0 else (1 if x > 0 else (0 if x == 0 else NaN))

Which for all intents and purposes may be simplified to:

sign = lambda x: -1 if x < 0 else (1 if x > 0 else 0)

This function definition executes fast and yields guaranteed correct results for 0, 0.0, -0.0, -4 and 5 (see comments to other incorrect answers).

Note that zero (0) is neither positive nor negative.

Carruth answered 16/9, 2018 at 14:26 Comment(5)
This answer illustrates how succinct yet powerful python can be.Greenwich
@JürgenStrobel I know exactly what you mean and I have also long been contemplating this issue. I extended the answer now for correct formalism, whilst maintaining the simplified version for most use cases.Carruth
(a > 0) - (a < 0) also complies.Dyslexia
@PaulCrowley Very nice! You should write your own answer and explain how this works.Carruth
Three answers are based on (a > 0) - (a < 0).Dyslexia
B
23

numpy has a sign function, and gives you a bonus of other functions as well. So:

import numpy as np
x = np.sign(y)

Just be careful that the result is a numpy.float64:

>>> type(np.sign(1.0))
<type 'numpy.float64'>

For things like json, this matters, as json does not know how to serialize numpy.float64 types. In that case, you could do:

float(np.sign(y))

to get a regular float.

Bub answered 27/8, 2014 at 0:41 Comment(0)
L
14

Try running this, where x is any number

int_sign = bool(x > 0) - bool(x < 0)

The coercion to bool() handles the possibility that the comparison operator doesn't return a boolean.

Louvenialouver answered 4/3, 2015 at 8:55 Comment(4)
Good idea, but I think you mean: int_sign = int(x > 0) - int(x < 0)Fluky
I mean: int_sign = lambda x: (x > 0) - (x < 0)Fluky
@Fluky no, he really meant the cast to bool (which is a subclass of int anyway), because of the theoretical possibility of which he gave the link to the explanation.Gastronomy
The only downside of this construct is that the argument appears twice, which is fine only if it's a single variableGastronomy
D
11

It just doesn't.

The best way to fix this is:

sign = lambda x: bool(x > 0) - bool(x < 0)

This sign function returns 1 for positive values, -1 for negative values and 0 for 0.0 and -0.0 (and NaNs...).

Dextrosinistral answered 17/4, 2020 at 18:23 Comment(1)
I'll pick your solution. Although mathematically irrelevant, it is more readable.Culch
S
10

In Python 2, cmp() returns an integer: there's no requirement that the result be -1, 0, or 1, so sign(x) is not the same as cmp(x,0).

In Python 3, cmp() has been removed in favor of rich comparison. For cmp(), Python 3 suggests this:

def cmp(a, b):
    return (a > b) - (a < b)

which is fine for cmp(), but again can't be used for sign() because the comparison operators need not return booleans.

To deal with this possibility, the comparison results must be coerced to booleans:

 def sign(x):
    return bool(x > 0) - bool(x < 0)

This works for any type which is totally ordered (including special values like NaN or infinities).

Sunwise answered 31/12, 2016 at 19:39 Comment(0)
A
7

Yes a correct sign() function should be at least in the math module - as it is in numpy. Because one frequently needs it for math oriented code.

But math.copysign() is also useful independently.

cmp() and obj.__cmp__() ... have generally high importance independently. Not just for math oriented code. Consider comparing/sorting tuples, date objects, ...

The dev arguments at http://bugs.python.org/issue1640 regarding the omission of math.sign() are odd, because:

  • There is no separate -NaN
  • sign(nan) == nan without worry (like exp(nan) )
  • sign(-0.0) == sign(0.0) == 0 without worry
  • sign(-inf) == -1 without worry

-- as it is in numpy

Aldo answered 18/8, 2016 at 10:56 Comment(0)
W
0

You dont need one, you can just use:

if not number == 0:
    sig = number/abs(number)
else:
    sig = 0

Or create a function as described by others:

sign = lambda x: bool(x > 0) - bool(x < 0)

def sign(x):
    return bool(x > 0) - bool(x < 0)
Wilow answered 16/10, 2014 at 8:13 Comment(3)
It bears pointing out that x / abs(x) takes slightly longer than just chaining if/else to check which side of 0 the variable is on, or for that matter using the slimy-yet-satisfying return (x > 0) - (x < 0) to subtract bool values and return an intDornick
Python treats True and False as 1 and 0, you can absolutely do this and get either 1, 0, or -1. def sign(x): return (x > 0) - (x < 0) won't return a bool, it'll return an int - if you pass 0 you'll get 0 backDornick
This is the only solution that works correctly for complex values.Ellon
I
0

Many cases listed in other answers overlook special cases (+/-0) or make assumption that sign(-0.0) == sign(0.0). It may be naïve, but with current implementation of IEEE we already have -0.0 == 0.0 and having sign() would allow us to disambiguate between the two.

Example provided by FogleBird seems to be best definition so far as it seems to handle +/- 0, INFINITY and NaN.

Invaginate answered 17/3, 2021 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.