Skip to content

Number Formats

This document specifies number formatting for internationalization in plain text accounting.

Input vs. Output

Input (Canonical)

Source files MUST use period as decimal separator:

1234.56       ; Canonical format

This format is: - Unambiguous (no locale dependency) - Consistent (same parsing rules everywhere) - Programming-friendly (standard decimal notation)

Output (Localized)

Reports and displays MAY use locale-specific formats:

1,234.56      ; en-US
1.234,56      ; de-DE
1 234,56      ; fr-FR
1'234.56      ; de-CH

Decimal Separator

Canonical (Period)

The period (.) is the ONLY valid decimal separator in source files:

100.00        ; Valid
100,00        ; Invalid in source

Locale Variants

Locale Separator Example
en-US . (period) 1234.56
en-GB . (period) 1234.56
de-DE , (comma) 1234,56
fr-FR , (comma) 1234,56
es-ES , (comma) 1234,56
de-CH . (period) 1234.56

Grouping Separator

Input Handling

Grouping separators in input MAY be accepted and are stripped:

1,234,567.89  ; Accepted, parsed as 1234567.89
1.234.567,89  ; NOT accepted (comma decimal + dot group)

Locale Variants

Locale Separator Example
en-US , (comma) 1,234,567
de-DE . (period) 1.234.567
fr-FR (space) 1 234 567
de-CH ' (apostrophe) 1'234'567
ja-JP , (comma) 1,234,567
hi-IN , (lakh system) 12,34,567

Grouping Size

System Pattern Example
Western 3,3,3... 1,234,567,890
Indian (lakh) 3,2,2... 1,23,45,67,890
Chinese (wan) 4,4,4... 12,3456,7890

Negative Numbers

Input Formats

-100.00       ; Prefix minus (canonical)
(100.00)      ; Parentheses (accepted)
100.00-       ; Suffix minus (rare)

Output Formats

Locale/Style Example
Standard -1,234.56
Accounting (1,234.56)
Suffix 1,234.56-

Accounting Format

Negative numbers in parentheses for financial reports:

Revenue         1,234.56
Expenses       (1,234.56)
─────────────────────────
Net                 0.00

Zero Handling

Positive/Negative Zero

0             ; Zero
-0            ; Negative zero (equals 0)
0.00          ; Zero with precision

Zero Display Options

Style Display
Standard 0.00
Dash -
Blank (empty)

Precision Display

Trailing Zeros

Input Display (2 places)
100 100.00
100.5 100.50
100.123 100.12 (rounded)

Maximum Precision

0.123456789   ; Preserve all digits
1000000000    ; Large numbers preserved

Currency-Specific Formatting

Minor Units

Currency Decimals Example
USD 2 100.00
EUR 2 100.00
JPY 0 100
KWD 3 100.000
BTC 8 0.00000001

Display Rules

100 USD       ; $100.00 (2 decimals)
100 JPY       ; ¥100 (0 decimals)
100 KWD       ; KD100.000 (3 decimals)

Percentage Display

As Decimal

0.15          ; 15% stored as decimal

As Percentage

15%           ; Display format
15.5%         ; With decimals

Scientific Notation

Not Used in PTA

Scientific notation is NOT used in plain text accounting:

1000000       ; Valid (one million)
1e6           ; Invalid in most PTA formats

Implementation Note

Parsers SHOULD reject scientific notation to prevent precision loss.

Configuration

Option Syntax

option "number_format" "1,234.56"    ; Pattern
option "locale" "de-DE"               ; Locale-based

Format Patterns

Pattern Meaning
#,##0.00 Comma grouping, period decimal, 2 places
#.##0,00 Period grouping, comma decimal, 2 places
# ##0,00 Space grouping, comma decimal, 2 places

Implementation

Formatting

import babel.numbers

def format_number(value: Decimal, locale: str = 'en_US') -> str:
    return babel.numbers.format_decimal(value, locale=locale)

# Examples:
# format_number(Decimal('1234.56'), 'en_US') → "1,234.56"
# format_number(Decimal('1234.56'), 'de_DE') → "1.234,56"

Parsing (Canonical)

import re
from decimal import Decimal

def parse_number(s: str) -> Decimal:
    """Parse canonical number format only."""
    # Strip grouping separators
    s = s.replace(',', '')

    # Validate format
    if not re.match(r'^-?\d+(\.\d+)?$', s):
        raise ParseError(f"Invalid number: {s}")

    return Decimal(s)

Locale-Aware Parsing

For user input (not source files):

import babel.numbers

def parse_locale_number(s: str, locale: str) -> Decimal:
    return babel.numbers.parse_decimal(s, locale=locale)

Alignment

Right Alignment

Numbers are typically right-aligned in reports:

Account              Debit    Credit
───────────────────────────────────────
Assets:Checking   1,000.00
Income:Salary                1,000.00

Decimal Alignment

Align decimal points for readability:

    1.00
   10.00
  100.00
1,000.00

Error Messages

Invalid Format

ERROR: Invalid number format
  --> ledger.beancount:42:18
   |
42 |   Assets:Cash  1.234,56 EUR
   |                ^^^^^^^^
   |
   = use period (.) as decimal separator

Overflow

ERROR: Number overflow
  --> ledger.beancount:42:15
   |
42 |   Assets:Cash  99999999999999999999999999999 USD
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = number exceeds maximum precision

Cross-Format Notes

Feature Beancount Ledger hledger
Decimal separator Period only Period only Period only
Grouping input Comma Comma Comma
Locale output Limited Yes Yes
Negative format Prefix - Prefix - Prefix -
Accounting format No Yes Yes