PHPOffice/PhpSpreadsheet · error · PhpSpreadsheet\Calculation\Exception

#NUM!

Error message

#NUM!

What it means

#NUM! from the places (padding width) validator shared by all base-conversion functions that take a places argument (BIN2HEX, BIN2OCT, DEC2BIN, DEC2HEX, DEC2OCT, HEX2BIN, HEX2OCT, OCT2BIN, OCT2HEX). ConvertBase::validatePlaces() rejects numeric places below 0 or above 10 with Calculation\Exception('#NUM!'), matching Excel's documented limit.

Source

Thrown at src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php:41

        if (is_numeric($value)) {
            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
                $value = floor((float) $value);
            }
        }

        return strtoupper(StringHelper::convertToString($value));
    }

    protected static function validatePlaces(mixed $places = null): ?int
    {
        if ($places === null) {
            return $places;
        }

        if (is_numeric($places)) {
            if ($places < 0 || $places > 10) {
                throw new Exception(ExcelError::NAN());
            }

            return (int) $places;
        }

        throw new Exception(ExcelError::VALUE());
    }

    /**
     * Formats a number base string value with leading zeroes.
     *
     * @param string $value The "number" to pad
     * @param ?int $places The length that we want to pad this value
     *
     * @return string The padded "number"
     */
    protected static function nbrConversionFormat(string $value, ?int $places): string
    {

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Keep places in the range 0..10 (it is truncated to int, so 3.9 becomes 3).
  2. For wider output, pad the returned string yourself with str_pad() after the conversion.
  3. Omit places (pass null) to get the minimum number of characters.
  4. Note nbrConversionFormat() additionally returns #NUM! when the result needs more digits than places allows.

Example fix

// before
$hex = ConvertBinary::toHex('10101010', 16); // '#NUM!' - places > 10

// after
$hex = str_pad(ConvertBinary::toHex('10101010'), 16, '0', STR_PAD_LEFT);
Defensive patterns

Strategy: validation

Validate before calling

$places = ($places === null) ? null : (int) $places;
if ($places !== null && ($places < 0 || $places > 10)) {
    throw new \InvalidArgumentException('places must be between 0 and 10');
}
$result = ConvertDecimal::toBinary($value, $places);

Type guard

/** places must be null or an integer 0..10. */
function isValidPlaces(mixed $places): bool
{
    return $places === null || (is_numeric($places) && $places >= 0 && $places <= 10);
}

Prevention

When it happens

Trigger: =BIN2HEX("1010", 11), =DEC2BIN(5, -1); ConvertHex::toBinary('1F', 12) from PHP; padding widths sourced from config that assume more than 10 output characters are possible (outputs are capped at 10 digits anyway).

Common situations: Wanting a fixed-width 16-character hex/binary output and passing 16; passing a negative 'unlimited' sentinel; computed widths from string length of the input plus padding.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/03408783a3940169. Report an issue: GitHub.