Humanizr/Humanizer · error · FormatException

A byte-size format can contain only one distinct unit token.

Error message

A byte-size format can contain only one distinct unit token.

What it means

Thrown by ByteSize.FindFormatUnit when the masked format selects two distinct higher-order unit tokens with different DataUnit values (for example both 'MB' and 'GB' in the same decimal format, or 'MiB' and 'GiB' in a binary format). A byte-size format may name exactly one scaling unit.

Source

Thrown at src/Humanizer/Bytes/ByteSize.cs:1048

        }

        if (maskedFormat.Contains("EiB", StringComparison.OrdinalIgnoreCase))
        {
            throw new FormatException("EiB is outside the range supported by ByteSize.Bits.");
        }

        SystemUnit? selectedUnit = null;
        var remainingFormat = maskedFormat;
        foreach (var unit in units)
        {
            if (!remainingFormat.Contains(unit.Symbol, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            if (selectedUnit is not null && selectedUnit.Value.DataUnit != unit.DataUnit)
            {
                throw new FormatException("A byte-size format can contain only one distinct unit token.");
            }

            selectedUnit = unit;
            remainingFormat = ReplaceOrdinalIgnoreCase(
                remainingFormat,
                unit.Symbol,
                new(' ', unit.Symbol.Length));
        }

        var containsBytes = remainingFormat.Contains(ByteSymbol, StringComparison.Ordinal);
        var containsBits = remainingFormat.Contains(BitSymbol, StringComparison.Ordinal);
        var tokenCount = (selectedUnit is null ? 0 : 1) + (containsBytes ? 1 : 0) + (containsBits ? 1 : 0);
        if (tokenCount > 1)
        {
            throw new FormatException("A byte-size format can contain only one distinct unit token.");
        }

        if (selectedUnit is not null)

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Use a single unit token per format string; render the second quantity in a separate ToString call.
  2. If you need a ratio, compute the numbers yourself and interpolate them into a plain string.
  3. Quote one of the tokens as a literal ("MB") if it is meant as decoration rather than the active unit.

Example fix

// before
var text = $"{used.ToString("#.## GB")} / {total.ToString("#.## GB")}"; // accidentally merged into one format

// after
var text = $"{used.ToString("#.## GB")} / {total.ToString("#.## GB")}"; // two separate calls
Defensive patterns

Strategy: validation

Validate before calling

static void EnsureSingleScalingUnit(string format, SystemUnit[] units)
{
    var matches = units.Count(u => format.Contains(u.Symbol, StringComparison.OrdinalIgnoreCase));
    if (matches > 1) throw new ArgumentException("Format contains more than one scaling unit token.");
}

Try / catch

try { return size.ToString(format); }
catch (FormatException) { throw new ArgumentException("Use exactly one scaling unit token per format.", nameof(format)); }

Prevention

When it happens

Trigger: Passing a format string like "#.## MB GB" or "# GiB MiB" to ByteSize.ToString. The loop over the active unit array sets selectedUnit, and on the next match with a different DataUnit it throws.

Common situations: Building a format string by concatenating two template fragments, templating errors where a placeholder leaks a unit token, or attempting to render a ratio like 'X MB per Y GB' in a single ToString call instead of two.

Related errors


AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13). Data as JSON: /api/errors/030e58b28348e03c. Report an issue: GitHub.