Question
Is there a standard C or C++ function equivalent to the mathematical sign function (signum or sgn)?
I need a function that returns -1 for negative numbers and +1 for positive numbers. In particular, I need it to work with floating-point values. How should zero be handled, and does the standard library provide such a function?
Short Answer
You will learn that standard C and C++ do not provide one universal signum function, how to write a clear and reusable version, and how floating-point edge cases such as 0.0, -0.0, and NaN affect the result.
Concept
A sign function classifies a number by whether it is negative, zero, or positive.
Mathematically, the usual definition is:
sign(x) = -1 when x < 0
sign(x) = 0 when x = 0
sign(x) = +1 when x > 0
Neither the C nor C++ standard library provides a single function named sign, sgn, or signum that returns -1, 0, or +1 for every numeric type.
For floating-point values, the standard libraries do provide related tools:
signbit(x)checks whether a floating-point value has a negative sign, including-0.0.copysign(magnitude, sign_source)returns a value with one number's magnitude and another number's sign.
Those functions are useful, but they are not the same as a three-way signum function. In most programs, writing a small comparison-based helper is the clearest solution.
The behavior for zero matters. A function that only returns -1 or +1 must decide what it should return for 0.0. A conventional signum function returns 0 for both 0.0 and -0.0.
Mental Model
Think of a sign function as a three-lane sorter for numbers:
- Numbers left of zero go into the negative lane and receive
-1. - Zero stays in the middle lane and receives
0. - Numbers right of zero go into the positive lane and receive
+1.
For floating-point numbers, -0.0 is a special item: it is still equal to zero in normal comparisons, even though it carries a negative sign bit internally. Whether you treat it as zero or negative depends on what your application means by “sign.”
Syntax and Examples
A compact, type-generic C++ implementation is:
#include <iostream>
template <typename T>
constexpr int signum(T value) {
return (T{0} < value) - (value < T{0});
}
int main() {
std::cout << signum(-3.5) << '\n'; // -1
std::cout << signum(0.0) << '\n'; // 0
std::cout << signum(8.2) << '\n'; // 1
}
Each comparison produces a Boolean value:
T{0} < valueistrue(1) for a positive value.value < T{0}istrue(1) for a negative value.
Subtracting them creates the result:
| Value category | (0 < value) | (value < 0) | Result |
|---|---|---|---|
| Negative | 0 | 1 | -1 |
| Zero | 0 | 0 | 0 |
| Positive | 1 | 0 | 1 |
A simple C version for double is:
int signum_double(double value) {
return (0.0 < value) - (value < 0.0);
}
If you specifically want -1.0 or +1.0 while preserving the sign bit, use copysign:
#include <cmath>
double signed_unit(double value) {
return std::copysign(1.0, value);
}
However, signed_unit(0.0) returns +1.0, and signed_unit(-0.0) returns -1.0, so it is not a conventional three-result signum function.
Step by Step Execution
Consider this call:
int result = signum(-4.25);
Using this function:
template <typename T>
constexpr int signum(T value) {
return (T{0} < value) - (value < T{0});
}
Step by step:
valueis-4.25.0.0 < -4.25isfalse, which becomes0in arithmetic.-4.25 < 0.0istrue, which becomes1in arithmetic.- The expression becomes
0 - 1. - The function returns
-1.
For signum(0.0):
0.0 < 0.0isfalse.0.0 < 0.0is alsofalsein the other comparison.- The expression is
0 - 0. - The function returns
0.
For signum(7.0), the expression is 1 - 0, so it returns 1.
Real World Use Cases
A signum helper is useful whenever only a value's direction matters:
- Movement and games: determine whether an object should move left (
-1) or right (+1) toward a target. - Sorting and comparison callbacks: normalize a comparison result to negative, zero, or positive.
- Finance: classify a balance, price change, or transaction as a loss, unchanged amount, or gain.
- Signal processing: identify whether a sampled signal is above or below zero.
- User input: convert a scroll, joystick, or keyboard direction into a normalized direction.
- Physics simulations: determine the direction of a force or velocity along one axis.
For example, a game character can move toward a target on the x-axis:
double direction = signum(targetX - playerX);
playerX += direction * speed;
If the player is already at the target, direction is 0, so no movement occurs.
Real Codebase Usage
In production code, developers usually give the helper a precise name and document its edge-case policy.
A typical C++ utility might be:
namespace math {
template <typename T>
constexpr int signum(T value) {
return (T{0} < value) - (value < T{0});
}
}
Then callers can use it in readable logic:
int direction = math::signum(currentIndex - previousIndex);
For floating-point validation, explicitly reject non-finite or NaN input when that is invalid for the domain:
#include <cmath>
#include <stdexcept>
double movementDirection(double distance) {
if (std::isnan(distance)) {
throw std::invalid_argument("distance must not be NaN");
}
return static_cast<double>((0.0 < distance) - (distance < 0.0));
}
This is a guard clause: it handles invalid input early instead of silently treating it as a normal value.
When an API requires a unit value with the same sign bit rather than a three-way classification, developers use std::copysign:
double correction = std::copysign(stepSize, error);
That pattern is useful when -0.0 needs to remain distinguishable from +0.0.
Common Mistakes
Forgetting zero
This implementation has no separate zero result:
int wrong_signum(double value) {
return value < 0.0 ? -1 : 1;
}
It returns 1 for 0.0, which may be incorrect. Use two comparisons when zero needs to map to 0:
int signum(double value) {
return (0.0 < value) - (value < 0.0);
}
Using integer division
int wrong_signum(int value) {
return value / std::abs(value);
}
This divides by zero when value is 0. It can also fail for the smallest representable signed integer because its positive magnitude may not be representable.
Assuming copysign is a three-way signum function
std::copysign(1.0, 0.0); // +1.0
std::copysign(1.0, -0.0); // -1.0
copysign copies a sign bit; it does not classify zero as 0.
Ignoring NaN
For a floating-point NaN, both ordered comparisons are false:
double value = std::numeric_limits<double>::quiet_NaN();
int result = (0.0 < value) - (value < 0.0); // 0
This does not mean that NaN is zero. If NaN is possible, check it with std::isnan in C++ or isnan in C before calling the helper, or define a dedicated policy.
Confusing -0.0 with a negative number
Normal comparisons treat -0.0 as equal to 0.0, so comparison-based signum(-0.0) returns 0. Use std::signbit(-0.0) only when the stored floating-point sign bit itself matters.
Comparisons
| Tool or approach | Main purpose | Result for 0.0 | Result for -0.0 | Notes |
|---|---|---|---|---|
(0 < x) - (x < 0) | Three-way sign classification | 0 | 0 | Best default signum implementation. |
x < 0 ? -1 : 1 | Two-way direction | 1 | -1 | Does not return zero. |
std::copysign(1.0, x) | Copy floating-point sign to unit magnitude | +1.0 | -1.0 | Preserves sign bit; useful for numerical code. |
std::signbit(x) | Inspect floating-point sign bit | false | true | Returns a Boolean-like result, not -1/0/+1. |
std::compare_three_way | Compare two values in modern C++ | Not a numeric sign result | Not a numeric sign result | Compares two objects rather than classifying one value. |
In C, <math.h> provides copysign and signbit for floating-point work. In C++, the corresponding functions are available through <cmath> as std::copysign and std::signbit.
Cheat Sheet
// Conventional signum: -1, 0, or +1
template <typename T>
constexpr int signum(T x) {
return (T{0} < x) - (x < T{0});
}
/* C version for double */
int signum_double(double x) {
return (0.0 < x) - (x < 0.0);
}
- Negative input: returns
-1. - Positive input: returns
+1. 0.0and-0.0: return0with comparison-based signum.NaN: both comparisons are false, so the compact expression returns0; validateNaNseparately if needed.- C++ sign-bit check:
std::signbit(x)from<cmath>. - C++ copy sign to a magnitude:
std::copysign(magnitude, x)from<cmath>. std::copysign(1.0, x)is not equivalent to three-way signum because zero returns either+1.0or-1.0.
FAQ
Does C have a standard signum() function?
No. Standard C does not define a general signum() function. You can implement it with comparisons, and <math.h> provides related functions such as signbit and copysign.
Does C++ have std::signum?
No. Standard C++ does not provide std::signum. A small constexpr comparison-based helper is common.
What should signum return for zero?
The conventional mathematical definition returns 0. If your program only needs a direction, you may choose another policy, but document it clearly.
What does a signum function return for -0.0?
A comparison-based implementation returns 0, because -0.0 == 0.0. Use std::signbit if you need to distinguish the negative zero sign bit.
Can I use std::copysign(1.0, value) as signum?
Only if you want a signed unit value and accept that zero returns +1.0 or -1.0. It is not the usual -1, 0, +1 signum behavior.
What happens when I pass NaN to the compact signum expression?
Both < comparisons are false, so it returns 0. Since NaN is not zero, validate it separately with std::isnan when it may occur.
Does the comparison-based implementation work for integers and floats?
Yes, provided the type supports comparison with zero. It works naturally for standard integer and floating-point types.
Mini Project
Description
Build a small C++ direction classifier for horizontal movement. It receives a current position and a target position, then reports whether to move left, stay still, or move right. This demonstrates how a signum function turns a numeric difference into a simple direction.
Goal
Create a program that uses a floating-point signum helper to classify movement toward a target position.
Requirements
Define a constexpr signum function that returns -1, 0, or 1.
Read or define floating-point current and target positions.
Calculate the distance as target - current.
Print left, already there, or right based on the sign.
Reject a NaN distance before classifying it.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.