# How to Format long double with Locale-Aware Decimal in fmtlib

> Learn how to format long double with locale-aware decimal separators in fmtlib using the L length modifier and std::locale for precise numeric output.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: how-to-guide
- Published: 2026-09-12

---

**Use the `L` length modifier with a `std::locale` argument in `fmt::format` to emit locale-specific decimal separators for `long` `double` values.**

In the fmtlib/fmt repository, formatting extended-precision floating-point numbers according to regional conventions requires understanding how the library delegates punctuation rules to the C++ locale system. The formatting engine uses a type-erased `locale_ref` wrapper to pass locale information through the template-heavy internals without heavy header dependencies. When you supply a `std::locale` to the formatting functions, the library queries that locale for decimal separators via internal helper functions before writing the digits.

## How Locale Propagation Works in fmtlib

The formatting pipeline begins in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h), which defines `locale_ref`. This lightweight abstraction wraps a `std::locale` pointer and allows the formatting engine to access locale data only when needed, avoiding the cost of including the heavy `<locale>` header in every translation unit.

When formatting a `long double`, the value flows through `detail::write<Char>(out, value, specs, loc)` defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h). This function receives the `locale_ref` object extracted from the `format_context`. Inside the write routine, the library calls `decimal_point_impl` (lines 1343–1348) to determine whether to use a comma, period, or other character as the decimal mark. Similarly, `thousands_sep_impl` (lines 1331–1335) retrieves the grouping separator when number formatting is requested.

## The Long Double Formatter Specialization

Unlike standard `double`, the `long double` type requires explicit handling through the length modifier system. In [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) around lines 4220–4224, the parser recognizes the `L` specifier and instantiates `detail::native_formatter` for the underlying `detail::float128` or `long double` type.

This specialization ultimately invokes `detail::write<Char>(out, value, specs, loc)`, forwarding the locale reference so that `decimal_point_impl` can emit the correct punctuation. Without the `L` modifier, the formatter treats the value as a standard floating-point type and may truncate precision or ignore extended exponent ranges.

## Formatting long double with Locale

To obtain locale-aware output, follow these steps:

1. Construct a `std::locale` object representing your target region.
2. Pass the locale as the first argument to `fmt::format`, `fmt::format_to`, or `fmt::vformat`.
3. Include the `L` length modifier in the format string. Combine with the `n` option for thousands separators.

```cpp
#include <fmt/format.h>
#include <locale>

int main() {
    // German locale uses comma as decimal separator
    std::locale german("de_DE.UTF-8");
    long double val = 1234.5678L;

    // Locale-aware decimal point only
    std::string s1 = fmt::format(german, "{:L}", val);
    // Result: "1234,5678"

    // Combining with 'n' for thousands separator
    std::string s2 = fmt::format(german, "{:nL}", val);
    // Result: "1 234,5678" (non-breaking space as thousands separator)
}

```

If you omit the locale argument, **fmtlib** falls back to the classic "C" locale, which always uses a period (`.`) as the decimal separator regardless of the platform or system settings.

## Key Implementation Files

- **[`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h)**: Contains `decimal_point_impl`, `thousands_sep_impl`, and the `formatter` specializations for floating-point types including `long double`.
- **[`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h)**: Defines `locale_ref`, the type-erased wrapper that transports locale data through the formatting pipeline without forcing `<locale>` inclusion.
- **[`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h)**: Demonstrates how `printf`-style APIs reuse the same `write` function and locale-aware helpers, confirming the unified architecture.

## Summary

- **Pass a `std::locale`** as the first argument to `fmt::format` to enable locale-aware punctuation.
- **Use the `L` modifier** to ensure `long double` values route through the correct `detail::native_formatter` specialization.
- **Combine with `n`** for full number formatting that includes thousands separators and the locale-specific decimal point.
- **Source implementation** resides in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) lines 1331–1348 for separator logic and lines 4220–4224 for the formatter specialization.

## Frequently Asked Questions

### What is the difference between the `L` and `n` format specifiers?

The **`L`** length modifier tells the parser to treat the argument as `long double` (or `float128` on supported platforms) and route it through the extended-precision formatter. The **`n`** option enables locale-aware number formatting, which includes both thousands separators and the locale-specific decimal separator. Use them together (`{:nL}`) for fully localized extended-precision output.

### Do I need to link against special libraries for locale support?

No. The **fmtlib** locale integration relies entirely on the C++ standard library's `<locale>` header. You only need to ensure your system has the requested locale installed (e.g., `de_DE.UTF-8`) and that you construct the `std::locale` object before passing it to formatting functions.

### Why does my output still show a period instead of a comma?

This occurs when you either omit the locale argument from `fmt::format` or pass a classic "C" locale. Without an explicit locale, **fmtlib** defaults to the C locale's convention of using `.` as the decimal separator. Always pass the locale object as the first parameter: `fmt::format(my_locale, "{:L}", value)`.

### Does this work for `__float128` or platform-specific extended types?

Yes. The `detail::native_formatter` specialization handles both `long double` and `detail::float128` (when `FMT_USE_FLOAT128` is defined) using the same `L` modifier. The locale-aware `write` routine processes these types identically, querying `decimal_point_impl` for the correct separator regardless of the underlying floating-point width.