Question

Sign Function (Signum) in C and C++: Floats, Zero, and NaN

cppcmath

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.”

Take Quiz

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} < value is true (1) for a positive value.
  • value < T{0} is true (1) for a negative value.

Subtracting them creates the result:

Value category(0 < value)(value < 0)Result
Negative01-1
Zero000
Positive101

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:

  1. value is -4.25.
  2. 0.0 < -4.25 is false, which becomes 0 in arithmetic.
  3. -4.25 < 0.0 is true, which becomes 1 in arithmetic.
  4. The expression becomes 0 - 1.
  5. The function returns -1.

For signum(0.0):

  1. 0.0 < 0.0 is false.
  2. 0.0 < 0.0 is also false in the other comparison.
  3. The expression is 0 - 0.
  4. 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.

Take Quiz

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 approachMain purposeResult for 0.0Result for -0.0Notes
(0 < x) - (x < 0)Three-way sign classification00Best default signum implementation.
x < 0 ? -1 : 1Two-way direction1-1Does not return zero.
std::copysign(1.0, x)Copy floating-point sign to unit magnitude+1.0-1.0Preserves sign bit; useful for numerical code.
std::signbit(x)Inspect floating-point sign bitfalsetrueReturns a Boolean-like result, not -1/0/+1.
std::compare_three_wayCompare two values in modern C++Not a numeric sign resultNot a numeric sign resultCompares 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.

Take Quiz

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.0 and -0.0: return 0 with comparison-based signum.
  • NaN: both comparisons are false, so the compact expression returns 0; validate NaN separately 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.0 or -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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more