jsguides

Math.sign()

Math.sign(x)

Math.sign() returns the sign of a number, indicating whether the value is positive, negative, zero, or NaN. It’s the quickest way to determine which direction a value points on the number line.

Syntax

Math.sign(x)

Parameters

ParameterTypeDefaultDescription
xnumberThe number to evaluate

Return Value

InputReturn Value
Positive number1
Negative number-1
0 (positive zero)0
-0 (negative zero)-0
NaNNaN

Why -0 Matters

The negative zero (-0) is a subtle but important edge case in JavaScript. While it behaves identically to 0 in most operations, it has distinct behavior in certain mathematical contexts:

// Basic comparisons treat them as equal
0 === -0;              // true
0 > -1;                // true

// But Object.is() distinguishes them
Object.is(0, -0);      // false
Object.is(-0, -0);     // true
Object.is(NaN, NaN);   // true

// Division behavior differs
1 / 0;    // Infinity
1 / -0;   // -Infinity

In physics engines and game development, preserving the sign of zero matters when calculating velocity, direction, or forces—it can determine whether an object moves left versus right, or whether a force pushes up versus down. The examples below show the basic return values for each input category before we apply them in real functions.

Examples

Basic usage

Math.sign(5);    // 1
Math.sign(-5);   // -1
Math.sign(0);   // 0
Math.sign(-0);  // -0
Math.sign(NaN); // NaN

// Works with non-integers too
Math.sign(3.14);   // 1
Math.sign(-2.71); // -1

The basic examples confirm the three return categories — positive, negative, and zero — but the real utility of Math.sign() emerges when you embed it into logic that acts on that result. Multiplying the sign by a magnitude turns a raw numeric difference into directional movement, which is the foundation of everything from game character movement to scroll-based animations.

Determining movement direction

In game development, Math.sign() cleanly determines which direction an entity should move:

function updateVelocity(currentPos, targetPos) {
  const direction = Math.sign(targetPos - currentPos);
  // direction = 1 (move right), -1 (move left), or 0 (already there)
  
  const speed = 5;
  return currentPos + direction * speed;
}

// Player at x=10, moving toward x=25
updateVelocity(10, 25); // returns 15

// Player at x=20, moving toward x=5
updateVelocity(20, 5);  // returns 15 (actually moves left due to direction)

The movement function assumes clean numeric inputs. In practice, user-supplied values, sensor readings, and computation results can be -0, NaN, or Infinity. Each of those special values produces a distinct return from Math.sign(), and ignoring them leads to bugs that are hard to spot because 0 and -0 compare as equal in most contexts.

Handling edge cases: -0, 0, and NaN

When working with user input or computed values, explicitly handle all return cases:

function getDirectionLabel(value) {
  const sign = Math.sign(value);
  
  if (sign === 1) return 'positive';
  if (sign === -1) return 'negative';
  if (Object.is(sign, -0)) return 'negative zero';
  if (sign === 0) return 'positive zero';
  return 'not a number'; // NaN case
}

getDirectionLabel(42);      // 'positive'
getDirectionLabel(-10);    // 'negative'
getDirectionLabel(0);      // 'positive zero'
getDirectionLabel(-0);     // 'negative zero'
getDirectionLabel(NaN);    // 'not a number'
getDirectionLabel(Infinity); // 'positive'

Once you’ve mapped every possible sign return to an application-level label, the next step is using Math.sign() in numeric transformations. The core pattern is splitting a value into its direction (sign) and magnitude (absolute value), operating on the magnitude, and then recombining them — this preserves orientation while modifying strength.

Normalizing vector components

In physics simulations, use Math.sign() to normalize direction while preserving axis information:

function applyForce(velocity, forceX, forceY) {
  return {
    x: velocity.x + forceX,
    y: velocity.y + forceY
  };
}

function clampVelocity(velocity, maxSpeed) {
  const speedX = Math.sign(velocity.x) * Math.min(Math.abs(velocity.x), maxSpeed);
  const speedY = Math.sign(velocity.y) * Math.min(Math.abs(velocity.y), maxSpeed);
  return { x: speedX, y: speedY };
}

const player = { x: -50, y: 25 };
clampVelocity(player, 30);
// { x: -30, y: 25 } - preserves direction, caps speed

The clamp pattern above combines Math.sign() with Math.abs() to cap speed while preserving the original direction. This pairing — sign for direction, magnitude for size — appears beyond physics engines: UI scroll velocity, audio waveform processing, and anywhere you need to constrain a value without inverting its sign. You can achieve the same result with manual if/else comparisons, but the code grows and the intent gets buried.

Math.sign() vs manual checks

// Using Math.sign() - concise and handles all cases
const sign = Math.sign(value);

// Manual if/else approach - more verbose
let sign;
if (value > 0) sign = 1;
else if (value < 0) sign = -1;
else sign = Object.is(value, -0) ? -0 : 0; // Handle -0 specially

// Ternary approach - can't distinguish 0 from -0
const sign2 = value > 0 ? 1 : value < 0 ? -1 : 0;
// This returns 0 for BOTH 0 and -0!

Math.sign() is cleaner than manual checks and is the only built-in way to reliably detect -0.

Math.sign() vs comparison operators

Manual sign detection with > and < works for most values but cannot distinguish 0 from -0. Math.sign() handles both zero variants correctly:

const x = -0;

// Manual check misses -0
x > 0 ? 1 : x < 0 ? -1 : 0;  // returns 0, not -0

// Math.sign() preserves the distinction
Math.sign(x);  // -0

For most practical code, 0 and -0 produce the same results and the distinction does not matter. In physics engines, animation systems, and numerical algorithms, preserving the sign of zero can affect the direction of subsequent calculations.

Multiplying by sign to flip values

A common pattern is multiplying Math.sign() by Math.abs() to reconstruct the original value, or using it to flip a value between positive and negative:

const speed = 10;

// Move in direction of target
const direction = Math.sign(target - current);
position += speed * direction;

// Cap absolute value while preserving sign
function clampAbs(value, max) {
  return Math.sign(value) * Math.min(Math.abs(value), max);
}

clampAbs(-50, 30);  // -30
clampAbs(15, 30);   // 15

The flip-and-clamp patterns above work well with clean numeric inputs, but Math.sign() also accepts non-numeric values through JavaScript’s implicit coercion rules. Strings, booleans, and null each produce a predictable result, while NaN and undefined propagate NaN. Knowing these coercion paths upfront prevents surprises when the function receives values you didn’t expect.

NaN propagation

If the argument is NaN, Math.sign() returns NaN. Non-numeric strings and undefined coerce to NaN:

Math.sign(NaN);        // NaN
Math.sign("hello");    // NaN
Math.sign(undefined);  // NaN
Math.sign(null);       // 0 (null coerces to 0)
Math.sign(true);       // 1 (true coerces to 1)

Always validate inputs before calling Math.sign() if you cannot guarantee the value is a number.

When Math.sign() is helpful

Math.sign() is especially useful when direction matters more than size. That makes it a natural fit for movement, comparisons, and any algorithm that needs to know whether a value sits above or below a baseline.

It is also handy when you need to preserve the sign while adjusting the magnitude. Pairing Math.sign() with Math.abs() lets you cap a number, flip it, or scale it without losing which side of zero it came from.

See Also