Sign Function in Python: sign/signum/sgn, copysign

Modified: | Tags: Python, Numeric

In Python, the built-in functions and standard libraries do not provide the sign function, i.e., the function that returns 1, -1, or 0 depending on the sign of a number. If you need such a function, you could use numpy.sign() from NumPy or define your own function.

The sign function (sign, signum, sgn)

The sign function returns 1 for positive numbers, -1 for negative numbers, and 0 for zero. You can get the sign of a number using this function.

As of version 3.11, Python does not include the sign function in its built-in functions or standard libraries.

See Stack Overflow and Python Bug Tracker below for reasons why the sign function is not provided in Python.

The developers chose to implement copysign() in the math module instead of sign() so that users can decide what value should be returned in edge cases such as +0.0, -0.0, NaN, and -NaN.

For more information on signed zero, see below.

In Python, the floating-point data type float can represent a negative zero, denoted as -0.0.

There is no distinction between positive and negative zeros in the integer type int; -0 is interpreted as 0.

print(0)
# 0

print(-0)
# 0

print(0 == -0)
# True

print(0 is -0)
# True

In contrast, float treats 0.0 and -0.0 as equivalent, yet distinct objects.

print(0.0)
# 0.0

print(-0.0)
# -0.0

print(0.0 == -0.0)
# True

print(0.0 is -0.0)
# False

numpy.sign()

NumPy provides numpy.sign(). If a sign function is required, numpy.sign() is a convenient choice.

numpy.sign() returns an int when given an int input, and a float when given a float input.

import numpy as np

print(np.sign(100))
# 1

print(np.sign(-100))
# -1

print(type(np.sign(100)))
# <class 'numpy.int64'>

print(np.sign(1.23))
# 1.0

print(np.sign(-1.23))
# -1.0

print(type(np.sign(1.23)))
# <class 'numpy.float64'>
source: my_sign.py

It returns zero for all zeros, including the negative zero -0.0.

print(np.sign(0))
# 0

print(np.sign(0.0))
# 0.0

print(np.sign(-0.0))
# 0.0
source: my_sign.py

It returns NaN for NaN.

print(np.sign(float('nan')))
# nan

print(np.sign(float('-nan')))
# nan
source: my_sign.py

numpy.sign() works with both scalar values and NumPy arrays (numpy.ndarray). Refer to the following article for more details.

Define the sign function in Python

With comparison operators

In Python, comparison operations typically return True and False. Since bool is a subclass of int, True can be treated as 1 and False as 0.

print(100 > 0)
# True

print(True - False)
# 1

print(False - True)
# -1

print(False - False)
# 0
source: my_sign.py

Based on this, you can define the sign function as follows:

def my_sign(x):
    return (x > 0) - (x < 0)
source: my_sign.py

The result is always an integer (int).

print(my_sign(100))
# 1

print(my_sign(-100))
# -1

print(type(my_sign(100)))
# <class 'int'>

print(my_sign(1.23))
# 1

print(my_sign(-1.23))
# -1

print(type(my_sign(-1.23)))
# <class 'int'>
source: my_sign.py

The function returns 0 for both zero and NaN, regardless of their signs.

print(my_sign(0))
# 0

print(my_sign(0.0))
# 0

print(my_sign(-0.0))
# 0

print(my_sign(float('nan')))
# 0

print(my_sign(float('-nan')))
# 0
source: my_sign.py

This function does not support complex numbers.

# print(my_sign(3 + 4j))
# TypeError: '>' not supported between instances of 'complex' and 'int'
source: my_sign.py

Note that some implementations of special methods such as __lt__ may return values other than True or False.

By convention, False and True are returned for a successful comparison. However, these methods can return any value, so if the comparison operator is used in a Boolean context (e.g., in the condition of an if statement), Python will call bool() on the value to determine if the result is true or false. 3. Data model - __lt__ — Python 3.11.3 documentation

When using built-in types like int and float, you don't need to worry about this, but caution is required when dealing with user-defined classes.

With abs()

You can also define the sign function using the built-in abs() function, which returns the absolute value of a number.

Here, the conditional expression is employed.

def my_sign_with_abs(x):
    return 0.0 if abs(x) == 0 else x / abs(x)
source: my_sign.py

This example returns a floating-point number (float) because it uses division / to support complex numbers, as described below.

print(my_sign_with_abs(100))
# 1.0

print(my_sign_with_abs(-100))
# -1.0

print(type(my_sign_with_abs(100)))
# <class 'float'>

print(my_sign_with_abs(1.23))
# 1.0

print(my_sign_with_abs(-1.23))
# -1.0

print(type(my_sign_with_abs(1.23)))
# <class 'float'>
source: my_sign.py

It returns 0.0 for zero and NaN for NaN.

print(my_sign_with_abs(0))
# 0.0

print(my_sign_with_abs(0.0))
# 0.0

print(my_sign_with_abs(-0.0))
# 0.0

print(my_sign_with_abs(float('nan')))
# nan

print(my_sign_with_abs(float('-nan')))
# nan
source: my_sign.py

Since abs() returns absolute values (distances on the complex plane) for complex numbers as well, this implementation also supports complex numbers.

print(abs(3 + 4j))
# 5.0

print(my_sign_with_abs(3 + 4j))
# (0.6+0.8j)
source: my_sign.py

It returns a unit vector pointing in the same direction as the original complex number.

The signum of a given complex number z is the point on the unit circle of the complex plane that is nearest to z. Sign function - Complex signum - Wikipedia

math.copysign()

While Python does not provide a sign() function, it does include the copysign() function in its math module.

math.copysign() returns a float with the absolute value of its first argument and the sign of its second argument.

import math

print(math.copysign(123, -100))
# -123.0

print(math.copysign(123.0, -100.0))
# -123.0

You can create a function similar to the sign function using math.copysign(). Note that its behavior differs from the typical sign function, as it doesn't return 0 for 0, as described below.

def my_sign_with_copysign(x):
    return int(math.copysign(1, x))

This example uses int() to always return an int.

print(my_sign_with_copysign(100))
# 1

print(my_sign_with_copysign(-100))
# -1

print(type(my_sign_with_copysign(100)))
# <class 'int'>

print(my_sign_with_copysign(1.23))
# 1

print(my_sign_with_copysign(-1.23))
# -1

print(type(my_sign_with_copysign(1.23)))
# <class 'int'>

It returns 1 for an integer zero, and 1 or -1 for a floating-point zero, depending on its sign.

print(my_sign_with_copysign(0))
# 1

print(my_sign_with_copysign(0.0))
# 1

print(my_sign_with_copysign(-0.0))
# -1

It returns 1 or -1 for NaN as well, depending on its sign.

print(my_sign_with_copysign(float('nan')))
# 1

print(my_sign_with_copysign(float('-nan')))
# -1

Since math.copysign() does not support complex numbers, this function does not support complex numbers.

# print(math.copysign(1, 3 + 4j))
# TypeError: can't convert complex to float

# print(my_sign_with_copysign(3 + 4j))
# TypeError: can't convert complex to float

Related Categories

Related Articles