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
- Check argument order: precision comes first, e.g. `{{ lang.FormatNumber 2 1234.56 }}` — the number is likely being used as the precision.
- Clamp or validate any computed precision so it is between 0 and 20.
- 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
- Clamp precision to a small non-negative integer (0–20) before calling
- Cast front-matter or params-sourced precision with `int` and validate it isn't negative
- Don't derive precision from user content without bounds checking
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
- too many fields in options parameter to NumFmt
- error building site: %w
- failed to detect format from content
- failed to detect target data serialization format
- templates.Defer cannot be used inside a partialCached partia
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/839a1e3b2228f946.json.
Report an issue: GitHub ↗.