How fmtlib Handles NaN and Infinity in C++ Floating-Point Formatting

fmtlib routes special floating-point values through write_nonfinite to output literal "nan" or "inf" strings, detecting them via detail::isnan and infinity range checks while respecting case specifiers and alignment rules.

The {fmt} library provides type-safe formatting for C++ applications, including standardized handling of IEEE-754 special values. When formatting NaN (Not a Number) or infinite floating-point values, fmtlib bypasses normal decimal conversion algorithms and writes canonical string representations. This implementation resides primarily in include/fmt/format.h and ensures consistent output across fmt::format, fmt::print, and related APIs.

Detecting Special Floating-Point Values in fmtlib

Before formatting occurs, fmtlib distinguishes between finite numbers and special values using custom detection logic. This ensures portability across different floating-point types and compilers.

NaN Detection via detail::isnan

The library uses a custom detail::isnan helper defined in include/fmt/format-inl.h rather than relying solely on std::isnan. This wrapper ensures compatibility across all floating-point types, including extended precision types like __float128 where standard library support may be unavailable.

During formatting, the main path at line 3682 of include/fmt/format.h invokes this check. If detail::isnan(value) returns true, the pipeline immediately flags the value as non-finite and skips the Dragonbox decimal conversion algorithm.

Infinity Checking in isfinite

For infinity detection, fmtlib compares the value against a compile-time constant inf derived from std::numeric_limits<T>::infinity(). The isfinite helper, located at lines 2857-2864 in include/fmt/format.h, evaluates whether the value lies within the finite range.

If the value is not NaN and fails the bounds check value < inf && value > -inf, fmtlib classifies it as infinite. This identifies both positive and negative infinity before the presentation type is selected.

The write_nonfinite Formatting Pipeline

Once a value is identified as non-finite, control flows to fmt::write_nonfinite at lines 2574-2579 of include/fmt/format.h. This function receives three critical parameters:

  • isnan: A boolean distinguishing NaN from infinity
  • sign: A flag indicating negative infinity (NaN has no sign)
  • Format specifications including the upper flag for case conversion

The function selects the appropriate literal string—"nan" for NaN or "inf" for infinity—and applies uppercase transformation when the format string contains the L flag (e.g., {:L}).

Padding and Alignment Rules

Non-finite values support width and alignment specifiers, but with a specific restriction. According to the is_zero_fill check at lines 8181-8184, zero-padding is replaced by space-padding for non-finite outputs. This prevents ambiguous representations like 000nan while preserving the requested field width and alignment.

Formatting Examples with Special Values

The following example demonstrates default lowercase output, uppercase conversion, and width alignment:

#include <fmt/core.h>
#include <cmath>
#include <limits>

int main() {
    double nan_val = std::nan("1");
    double pos_inf = std::numeric_limits<double>::infinity();
    double neg_inf = -std::numeric_limits<double>::infinity();

    // Default output: lowercase nan/inf
    fmt::print("Default: {} {} {}\n", nan_val, pos_inf, neg_inf);
    // Output: nan inf -inf

    // Uppercase specifier with 'L' flag
    fmt::print("Upper: {:L} {:L} {:L}\n", nan_val, pos_inf, neg_inf);
    // Output: NAN INF -INF

    // Width and alignment (10 characters wide, right-aligned)
    fmt::print("Aligned: '{:>10}' '{:>10}'\n", nan_val, pos_inf);
    // Output: '       nan' '       inf'
}

Chrono Library Integration

The formatting logic extends to time calculations through include/fmt/chrono.h. When formatting chrono durations or time points containing NaN values, the library forwards to a specialized write_nan function that outputs "nan" directly. This maintains consistency with the core floating-point formatting pipeline across all fmtlib APIs.

Summary

  • Detection: fmtlib identifies NaN via detail::isnan and infinity via range checks against std::numeric_limits<T>::infinity() in include/fmt/format.h.
  • Output: Special values route through write_nonfinite (lines 2574-2579) to produce literal "nan" or "inf" strings.
  • Case Control: The L format specifier converts output to uppercase (NAN, INF).
  • Padding: Zero-fill is disabled for non-finite values, automatically converting to space-padding per lines 8181-8184.
  • Consistency: The same logic applies across fmt::format, fmt::print, and chrono formatting APIs.

Frequently Asked Questions

How does fmtlib format negative infinity?

When formatting negative infinity, fmtlib detects the sign during the isfinite check and passes it to write_nonfinite. The function prepends a minus sign to the "inf" literal, producing "-inf" by default or "-INF" when using the uppercase L specifier.

Can I customize the string output for NaN values in fmtlib?

No, fmtlib uses hardcoded literals "nan" and "NAN" within the write_nonfinite function. Unlike finite floating-point formatting that supports precision and notation specifiers, non-finite values have fixed string representations defined in the source code at include/fmt/format.h.

Does fmtlib handle NaN for extended precision types like __float128?

Yes, the custom detail::isnan implementation in include/fmt/format-inl.h provides portability for floating-point types beyond standard double and float, including __float128 where std::isnan might not be available.

Why does zero-padding not work with NaN and infinity in fmtlib?

According to lines 8181-8184 in include/fmt/format.h, fmtlib explicitly replaces zero-fill with space-fill for non-finite values. This design choice prevents confusing numeric representations (like 0000nan) while maintaining the specified field width and alignment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →