number Number Parameter

Complete Guide to Number Formatting - Style, Compact, Precision

Overview

The number parameter type formats numbers with flexible options for style, precision, and compact notation. It supports various format styles including decimal, integer, percent, currency, and unit.

Number Format Options

The numberFormat object controls how numbers are displayed:

{
  "paramName": "amount",
  "type": "number",
  "numberFormat": {
    "style": "decimal",       // decimal, integer, percent, currency, unit
    "percent": "percent",     // percent, permille
    "compact": "short",       // "short", "long" - adds K/W/M suffix
    "precisionLength": 2,     // number of decimal places, -1 for auto
    "precisionMode": "max",    // "max" to strip trailing zeros
    "unit": "kg",             // unit suffix for "unit" style
    "currency": "USD"         // currency code for "currency" style
  }
}

Format Types

The style option specifies the number format type.

Style Description Default Precision Example
decimal Standard decimal number formatting -1 (auto) 1,234.56
integer Integer without decimals 0 1234
percent Percentage formatting (multiplied by 100) 2 12.34%
currency Currency formatting with symbol 2 $1,234.56
unit Number with custom unit suffix -1 (auto) 100kg

Precision Control

The precisionLength option controls the number of decimal places:

precisionLength Behavior
-1 Auto - uses style-specific default (integer=0, percent/currency=2, others=-1)
0 No decimal places
2 Exactly 2 decimal places
3 Exactly 3 decimal places

precisionMode

When precisionMode is set to "max", trailing zeros after the decimal point are stripped.

precisionMode: "max" Example

Input: 1234.50 with precisionLength: 2, precisionMode: "max"

Output: "1234.5" (trailing zero removed)

precisionMode: "max" Example 2

Input: 1234.00 with precisionLength: 2, precisionMode: "max"

Output: "1234" (decimal point also removed)

Percent Style

The percent option controls whether to use percent or permille:

percent Multiplier Symbol Example
"percent" 100 % 0.25 → "25%"
"permille" 1000 0.25 → "250‰"

Compact Notation

When compact is set to "short" or "long", large numbers are abbreviated with a suffix:

≥ 1,000,000
÷ 1,000,000 + "M"
1,500,000 → "1.5M"
≥ 10,000
÷ 10,000 + "W"
15,000 → "1.5W"
≥ 1,000
÷ 1,000 + "K"
1,500 → "1.5K"

Compact Examples

// Input: 1234567 with compact: "short"
// Output: "1.234567M"

// Input: 12345 with compact: "short"
// Output: "1.2345W"

// Input: 1234 with compact: "short"
// Output: "1.234K"

// Input: 999 with compact: "short"
// Output: "999" (no compacting applied)

Language Implementation Comparison

Different programming languages provide different levels of support for number formatting features.

Language Styles Compact Precision Percent
Swift decimal, integer, percent, currency, unit ✅ K/W/M ✅ percent/permille
Python decimal, integer, percent, currency, unit ✅ K/W/M ✅ percent/permille
TypeScript decimal, integer, percent, currency, unit ✅ K/W/M ✅ percent/permille
Java decimal, integer, percent, currency, unit ✅ K/W/M ✅ percent/permille

Generated Code Implementation

Swift Implementation

static func formatNumber(_ value: Any, _ p: I18NParam) -> String {
    let numberFormat = p.numberFormat
    let style = numberFormat?.style ?? "decimal"
    let percentStyle = numberFormat?.percent ?? "percent"
    let compact = numberFormat?.compact ?? ""
    let precisionLength = numberFormat?.precisionLength ?? -1
    let precisionMode = numberFormat?.precisionMode ?? ""
    let unit = numberFormat?.unit ?? ""
    let num = (value as? NSNumber)?.doubleValue ?? Double("\(value)") ?? 0.0
    var displayNum = num
    var suffix = ""

    if style == "percent" {
        let multiplier = percentStyle == "permille" ? 1000.0 : 100.0
        displayNum = num * multiplier
    }

    if !compact.isEmpty && displayNum >= 1000 {
        if displayNum >= 1_000_000 {
            displayNum = displayNum / 1_000_000
            suffix = "M"
        } else if displayNum >= 10_000 {
            displayNum = displayNum / 10_000
            suffix = "W"
        } else {
            displayNum = displayNum / 1_000
            suffix = "K"
        }
    }

    let effectivePrecision = precisionLength >= 0 ? precisionLength
        : style == "integer" ? 0
        : (style == "percent" || style == "currency") ? 2
        : -1

    var formatted: String
    if effectivePrecision >= 0 {
        let formatter = NumberFormatter()
        if style == "decimal" { formatter.numberStyle = .decimal }
        if style == "integer" { formatter.numberStyle = .none }
        if style == "currency" { formatter.numberStyle = .currency }
        formatter.minimumFractionDigits = effectivePrecision
        formatter.maximumFractionDigits = effectivePrecision
        formatted = formatter.string(from: NSNumber(value: displayNum))
            ?? String(format: "%.\(effectivePrecision)f", displayNum)

        if precisionMode == "max" && formatted.contains(".") {
            while formatted.hasSuffix("0") && formatted.contains(".") {
                formatted.removeLast()
            }
            if formatted.hasSuffix(".") { formatted.removeLast() }
        }
    } else {
        if displayNum == Double(Int(displayNum)) && displayNum.isFinite {
            formatted = String(Int(displayNum))
        } else {
            formatted = String(displayNum)
        }
    }

    let body = formatted + suffix
    if style == "percent" {
        if percentStyle == "permille" { return body + "‰" }
        return body + "%"
    }
    if style == "currency" {
        let cf = NumberFormatter()
        cf.numberStyle = .currency
        return cf.string(from: NSNumber(value: displayNum)) ?? body
    }
    if style == "unit" { return body + unit }
    return body
}

Python Implementation

@staticmethod
def format_number(value: float, p: dict) -> str:
    opts = p.get("numberFormat", {})
    style = opts.get("style", "decimal")
    precision = opts.get("precisionLength", -1)
    precision_mode = opts.get("precisionMode", "")
    compact = opts.get("compact", "")
    num = float(value)
    display_num = num
    suffix = ""

    if style == "percent":
        multiplier = 1000.0 if opts.get("percent") == "permille" else 100.0
        display_num = num * multiplier

    if compact and display_num >= 1000:
        if display_num >= 1_000_000:
            display_num = display_num / 1_000_000
            suffix = "M"
        elif display_num >= 10_000:
            display_num = display_num / 10_000
            suffix = "W"
        else:
            display_num = display_num / 1_000
            suffix = "K"

    effective_precision = precision if precision >= 0 else (
        0 if style == "integer" else (
        2 if style in ("percent", "currency") else -1))

    if effective_precision >= 0:
        formatted = f"{display_num:.{effective_precision}f}"
        if precision_mode == "max" and "." in formatted:
            formatted = formatted.rstrip("0").rstrip(".")
    else:
        if display_num == int(display_num) and not math.isinf(display_num):
            formatted = str(int(display_num))
        else:
            formatted = str(display_num)

    body = formatted + suffix
    if style == "percent":
        return body + "%"
    if style == "currency":
        return (opts.get("currency") or "¥") + body
    if style == "unit":
        return body + (opts.get("unit") or "")
    return body

Complete Examples

JSON Parameter Definition (Decimal with Precision)

{
  "paramName": "amount",
  "type": "number",
  "numberFormat": {
    "style": "decimal",
    "precisionLength": 2,
    "precisionMode": "max"
  }
}

Input: 1234.50Output: "1234.5"

Input: 1234.00Output: "1234"

JSON Parameter Definition (Percent)

{
  "paramName": "discount",
  "type": "number",
  "numberFormat": {
    "style": "percent",
    "percent": "percent",
    "precisionLength": 2
  }
}

Input: 0.25Output: "25%"

Input: 0.1234Output: "12.34%"

JSON Parameter Definition (Percent Permille)

{
  "paramName": "ratio",
  "type": "number",
  "numberFormat": {
    "style": "percent",
    "percent": "permille"
  }
}

Input: 0.25Output: "250‰"

JSON Parameter Definition (Currency)

{
  "paramName": "price",
  "type": "number",
  "numberFormat": {
    "style": "currency",
    "currency": "USD"
  }
}

Input: 1234.56Output: "$1234.56" (Swift with locale)

Input: 1234.56Output: "¥1234.56" (Python default)

JSON Parameter Definition (Compact)

{
  "paramName": "views",
  "type": "number",
  "numberFormat": {
    "style": "decimal",
    "compact": "short"
  }
}

Input: 1500000Output: "1.5M"

Input: 15000Output: "1.5W"

Input: 1500Output: "1.5K"

JSON Parameter Definition (Unit)

{
  "paramName": "weight",
  "type": "number",
  "numberFormat": {
    "style": "unit",
    "unit": "kg"
  }
}

Input: 100Output: "100kg"

✅ Compact + Percent Combined

Compact and percent styles can be combined. The percent multiplier is applied first, then compact notation:

{
  "paramName": "conversion",
  "type": "number",
  "numberFormat": {
    "style": "percent",
    "compact": "short"
  }
}

Input: 0.15Output: "15%" (not compacted, below 1000 after multiplier)

Input: 1.5Output: "150%"