gohugoio/hugo · error

invalid precision: %d

Error message

invalid precision: %d

What it means

Hugo's number-formatting functions (`lang.FormatNumber`, `FormatPercent`, `FormatCurrency`, `FormatAccounting`) share `castPrecisionNumber`, which sanity-checks the precision argument and rejects any value greater than 20. Precisions above 20 exceed float64's meaningful decimal digits, so Hugo fails fast instead of producing garbage output.

Source

Thrown at tpl/lang/lang.go:115

//
// The return value is formatted with at least two decimal places.
func (ns *Namespace) FormatAccounting(precision, currency, number any) (string, error) {
	p, n, err := ns.castPrecisionNumber(precision, number)
	if err != nil {
		return "", err
	}
	return ns.translator.FormatAccounting(n, p, cast.ToString(currency)), nil
}

func (ns *Namespace) castPrecisionNumber(precision, number any) (int, float64, error) {
	p, err := cast.ToIntE(precision)
	if err != nil {
		return 0, 0, err
	}

	// Sanity check.
	if p > 20 {
		return 0, 0, fmt.Errorf("invalid precision: %d", precision)
	}

	n, err := cast.ToFloat64E(number)
	if err != nil {
		return 0, 0, err
	}
	return p, n, nil
}

// FormatNumberCustom formats a number with the given precision. The first
// options parameter is a space-delimited string of characters to represent
// negativity, the decimal point, and grouping. The default value is `- . ,`.
// The second options parameter defines an alternate delimiting character.
//
// Note that numbers are rounded up at 5 or greater.
// So, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.
//
// For a simpler function that adapts to the current language, see FormatNumber.

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Check argument order: precision comes first, e.g. `{{ lang.FormatNumber 2 1234.56 }}` — the number is likely being used as the precision.
  2. Clamp or validate any computed precision so it is between 0 and 20.
  3. If the value comes from front matter or config, verify it is the intended decimal-places integer, not the number to format.

Example fix

<!-- before (args swapped) -->
{{ lang.FormatNumber 1234.56 2 }}
<!-- after -->
{{ lang.FormatNumber 2 1234.56 }}
Defensive patterns

Strategy: validation

Validate before calling

{{ $p := 2 }}
{{ if and (ge $p 0) (le $p 20) }}{{ lang.FormatNumber $p .Value }}{{ else }}{{ errorf "precision out of range: %v" $p }}{{ end }}

Type guard

{{ $ok := and (eq (printf "%T" $p) "int") (ge $p 0) }}

Try / catch

{{ with try (lang.FormatNumber $p .Value) }}{{ with .Err }}{{ warnf "bad precision %v: %s" $p . }}{{ else }}{{ .Value }}{{ end }}{{ end }}

Prevention

When it happens

Trigger: Calling `{{ lang.FormatNumber 21 512.5032 }}` or any of the four Format* functions with a precision argument that casts to an int greater than 20. Note only the upper bound is checked here; a non-numeric precision fails earlier with a cast error.

Common situations: Swapped argument order — Hugo's Format* functions take precision FIRST, so `{{ lang.FormatNumber 1234.56 2 }}` passes 1234 as precision and triggers this; also computed precisions from site params or front matter that are unexpectedly large.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/839a1e3b2228f946.json. Report an issue: GitHub ↗.