package display import ( "fmt" "io" "math" "strconv" "strings" ) // FormatPrice renders a price for display. Values >= 1 keep two decimals for // readability; smaller values use the shortest exact representation so tiny // prices like 0.00000059 are not collapsed to 0.00. func FormatPrice(v float64) string { if v == 0 { return "0.00" } if math.Abs(v) >= 1 { return strconv.FormatFloat(v, 'f', 2, 64) } return strconv.FormatFloat(v, 'f', -1, 64) } // Decimal is a float64 that prints without collapsing sub-0.01 values to 0.00 // and without scientific notation. Templates keep using {{printf "%.2f" .price}} // and {{.price}}; wrapping happens at render time so stored data stays numeric. type Decimal float64 func (d Decimal) Format(f fmt.State, verb rune) { v := float64(d) switch verb { case 'f', 'F': prec := 6 if p, ok := f.Precision(); ok { prec = p } s := strconv.FormatFloat(v, byte(verb), prec, 64) if v != 0 && isCollapsedZero(s) { s = strconv.FormatFloat(v, 'f', -1, 64) } _, _ = io.WriteString(f, s) case 'v', 's': _, _ = io.WriteString(f, formatPlain(v)) default: prec := -1 if p, ok := f.Precision(); ok { prec = p } _, _ = io.WriteString(f, strconv.FormatFloat(v, byte(verb), prec, 64)) } } func formatPlain(v float64) string { if v == 0 { return "0" } return strconv.FormatFloat(v, 'f', -1, 64) } func isCollapsedZero(s string) bool { t := strings.TrimPrefix(s, "-") t = strings.TrimPrefix(t, "+") if t == "" { return false } sawZero := false for _, r := range t { switch r { case '0': sawZero = true case '.': default: return false } } return sawZero } // WrapMap copies data and wraps float values so template printing keeps // precision. The original map is left unchanged for condition evaluation. func WrapMap(data map[string]interface{}) map[string]interface{} { if data == nil { return nil } out := make(map[string]interface{}, len(data)) for k, v := range data { out[k] = wrapValue(v) } return out } func wrapValue(v any) any { switch t := v.(type) { case nil: return nil case float64: return Decimal(t) case float32: return Decimal(t) case Decimal: return t case map[string]interface{}: return WrapMap(t) case []interface{}: out := make([]interface{}, len(t)) for i, x := range t { out[i] = wrapValue(x) } return out default: return v } }