Metadata-Version: 2.4
Name: larzmoney
Version: 0.1.0
Summary: Exact, penny-perfect money for Python. Integer minor units, never a float. Zero dependencies.
Author: larz-scripter
License: MIT
Project-URL: Homepage, https://github.com/larz-scripter/larzmoney
Project-URL: Repository, https://github.com/larz-scripter/larzmoney
Project-URL: Documentation, https://github.com/larz-scripter/larzmoney#readme
Project-URL: Issues, https://github.com/larz-scripter/larzmoney/issues
Keywords: money,currency,decimal,finance,fintech,allocation,rounding,exchange-rate,iso4217,zero-dependency
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# larzmoney

**Exact, penny-perfect money for Python. Zero dependencies.**

A `Money` is an integer count of minor units plus a currency, and it **never
becomes a float**. `$10.99` is stored as the integer `1099`, not the float
`10.99` (which doesn't exist exactly in binary and is the root of every
"off by a cent" bug). All arithmetic is integer arithmetic; rounding only
happens where you multiply or divide, and there you choose the mode.

```python
from larzmoney import Money

price = Money.of("19.99", "USD")
tax   = price.percent("8.25")      # Money('1.65', 'USD')
total = price + tax                # Money('21.64', 'USD')

# split three ways with not one cent lost
total.split(3)                     # [Money('7.22','USD'), Money('7.21','USD'), Money('7.21','USD')]
# ...which sums back to exactly 21.64
```

- **Zero dependencies.** Pure standard library (`decimal`, `int`). Nothing to audit but this repo.
- **Floats are rejected, loudly.** `Money.of(10.99, "USD")` raises. Pass a string.
- **Penny-perfect allocation.** `split()` / `allocate()` always sum back to the original.
- **Real currencies.** ISO 4217 exponents baked in — 2 for dollars, 0 for yen, 3 for dinars, 8 for BTC. Register your own (loyalty points, in-game gold).
- **Explicit rounding.** HALF_UP, banker's HALF_EVEN, UP, DOWN, CEILING, FLOOR.
- **Serializes cleanly.** `to_dict()` / `from_dict()` round-trip through JSON with the exact integer preserved.

## Install

```bash
pip install larzmoney
```

## Why not just use floats (or `Decimal`)?

```python
>>> 0.1 + 0.2
0.30000000000000004        # floats: money silently drifts
>>> from decimal import Decimal
>>> Decimal("0.10") + Decimal("0.20")
Decimal('0.30')            # better — but nothing stops you splitting $10 three
                           # ways and losing a cent, or adding USD to EUR
```

`Decimal` fixes the representation but not the *domain*: it won't stop you mixing
currencies, it won't round to the right number of places for the currency, and
it won't split a bill without leaking fractions. `larzmoney` is `Decimal` done
up as actual money.

## The allocation guarantee

The reason this library exists. Splitting money is not division — you can't hand
someone a third of a cent. `allocate` distributes the remainder fairly so the
parts **always sum back to the original**:

```python
>>> from larzmoney import Money, sum_money
>>> parts = Money.of("10.00", "USD").split(3)
>>> parts
[Money('3.34', 'USD'), Money('3.33', 'USD'), Money('3.33', 'USD')]
>>> sum_money(parts) == Money.of("10.00", "USD")
True

>>> Money.of("0.05", "USD").allocate([3, 7])     # weighted split
[Money('0.02', 'USD'), Money('0.03', 'USD')]
```

Use it for invoice line splits, marketplace fee/seller payouts, tax
apportionment, group bills — anywhere the parts must reconcile to the whole.

## Rounding modes

```python
from larzmoney import Money, HALF_EVEN

Money.of("1.005", "USD")                       # 1.01  (HALF_UP, the default)
Money.of("1.005", "USD", rounding=HALF_EVEN)   # 1.00  (banker's rounding)
Money.of("10.00", "USD").multiply("0.0825")    # 0.83  sales tax, rounded
```

Modes: `HALF_UP`, `HALF_EVEN`, `HALF_DOWN`, `UP`, `DOWN`, `CEILING`, `FLOOR`
(pass the constant or its name, e.g. `rounding="HALF_EVEN"`).

## Currency conversion

larzmoney doesn't fetch rates (that would break zero-dependency and bake in an
opinion). You supply the rates you trust; it does the exact arithmetic:

```python
from larzmoney import Money, ExchangeRates

fx = ExchangeRates("USD").add("EUR", "0.92").add("GBP", "0.79")
fx.convert(Money.of("100", "USD"), "EUR")      # Money('92.00', 'EUR')
fx.convert(Money.of("100", "EUR"), "GBP")      # cross-rated through USD
```

## Custom currencies

```python
from larzmoney import Money, Currency, register

register(Currency("GOLD", exponent=0, symbol="🪙", name="Game Gold"))
Money.of("500", "GOLD").format()               # '🪙500'
```

## API at a glance

| | |
|---|---|
| `Money.of(amount, ccy, rounding=HALF_UP)` | build from a human amount (`"10.99"`) |
| `Money(minor_units, ccy)` / `Money.from_minor(...)` | build from cents |
| `Money.zero(ccy)` | zero in a currency |
| `.minor_units` / `.amount` | exact `int` / exact `Decimal` |
| `+ - * /`, `.multiply()`, `.divide()`, `.percent()` | arithmetic (scalars, not floats) |
| `.split(n)` / `.allocate(ratios)` | penny-perfect splitting |
| `<  <=  >  >=  ==`, `.sign()`, `.is_zero/positive/negative()` | comparison |
| `.format(symbol=, grouping=, code=)` | display string |
| `.to_dict()` / `Money.from_dict()` | JSON-safe round-trip |
| `sum_money(iterable, currency=None)` | safe sum |
| `ExchangeRates(base).add(ccy, rate).convert(m, to)` | conversion |

## Tests

```bash
python -m unittest discover -s tests -v      # 68 tests, zero dependencies
```

## The Larz stack

Part of a family of pure-Python, zero-dependency building blocks:

- **[larz](https://github.com/larz-scripter/larz)** — money-native web framework
- **[larzchain](https://github.com/larz-scripter/larzchain)** — from-scratch PoW blockchain
- **larzmoney** — this library

## License

MIT © larz-scripter
