Home / Articles / Magic Numbers

Magic Numbers

What Are Magic Numbers?

Magic numbers are literal numeric values used directly in a program — appearing in expressions, array sizes, and as constants. Examples:

char buf[1024];              /* 1K buffer size */
int retransmissionTimer = 100; /* Re-transmit after 100 ms */

The literals 1024 and 100 are magic numbers. The literals 0 and 1 are usually excluded from this category.

What's Wrong with Magic Numbers?

Magic numbers are confusing because a number placed in the middle of an expression gives little indication of its meaning or purpose. Consider this snippet from a character-mode text editor designed for 25 rows and 80 columns (with borders reserving the first/last row and column):

void moveLeft()
{
    if (curCol == 79) {
        curCol = 2;
        if (curRow == 24) {
            curRow = 3;
            shiftScreenUp(1);
        } else {
            curRow++;
        }
    } else {
        curCol++;
    }
    displayCursor();
}

None of the layout constraints are conveyed by the literals 79, 2, 24, or 3. To a new reader, this looks like it might contain bugs.

Avoid Magic Numbers Where Possible

Some magic numbers can be avoided entirely by using language features. For example, checking whether a character is alphabetic:

if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))

can be replaced with:

if (isalpha(ch))

Similarly, use NULL instead of 0 for null pointers, and use sizeof instead of hardcoding data type sizes.

Give Names to Magic Numbers

When literals cannot be avoided, give them names that convey their purpose. Compare:

if (timer.curMSecs > (timer.info.StartMSecs + 100))
    deleteTimer(timer);

with:

#define maxRetransmitTime 100
...
if (timer.curMSecs > (timer.info.StartMSecs + maxRetransmitTime))
    deleteTimer(timer);

Benefits of naming literals:

  1. Clarity — the name explains the meaning without requiring a comment.
  2. Maintainability — changing the value requires a single edit instead of hunting through the entire codebase.
  3. Fewer errors — no risk of forgetting to update one of many scattered occurrences.

Use Macros

Macros are the most common way to name constants in C. The text-editor example rewritten with macros:

#define MAXROW 24  /* Last row for border */
#define MINROW 3   /* First row for menu and second row for border */
#define MAXCOL 79  /* Last column for border */
#define MINCOL 2   /* First column for border */

void moveLeft()
{
    if (curCol == MAXCOL) {
        curCol = MINCOL;
        if (curRow == MAXROW) {
            curRow = MINROW;
            shiftScreenUp(1);
        } else {
            curRow++;
        }
    } else {
        curCol++;
    }
    displayCursor();
}

Use Enumerations

For large applications with many macros, enumerations can logically group related literals:

typedef enum screenSize {
    MAXROW = 24,  /* Last row for border */
    MINROW = 3,   /* First row for menu and second row for border */
    MAXCOL = 79,  /* Last column for border */
    MINCOL = 2    /* First column for border */
} screenSize;

Drawbacks: enumerations in C lack type safety and can only hold integer values.

Use Constants

Constants are type-safe and support all data types:

const int MAXROW = 24;  /* Last row for border */
const int MINROW = 3;   /* First row for menu and second row for border */
const int MAXCOL = 79;  /* Last column for border */
const int MINCOL = 2;   /* First column for border */

Variables (rather than constants) should be used only when there is a strong reason to do so, such as when the value needs to be passed as a parameter to achieve generality:

void moveLeft(int MAXROW, int MAXCOL, int MINROW, int MINCOL)
{
    if (curCol == MAXCOL) {
        curCol = MINCOL;
        ...
    }
    ...
}