The first pair of lines is the trap. The rest is the formatting you need.
print(divmod(1234, 100)) # -> (12, 34) as expected
print(divmod(-1234, 100)) # -> (-13, 66) not (-12, 34)
print(f'{1234567:,}') # -> '1,234,567' comma grouping
print(f'{5:02d}') # -> '05' padded to two digits
print(f'{34:02d}') # -> '34' already two, unchanged
- divmod floors, so negatives split somewhere surprising
divmod(-1234, 100) gives (-13, 66) because floor division rounds towards negative infinity: -13 hundreds plus 66 is -1234. Mathematically impeccable and useless for display — you wanted minus twelve pounds thirty-four. Taking abs() first and putting the sign back at the end avoids the whole question.
- The comma is a format spec, not string surgery
f'{value:,}' inserts thousands separators for you, correctly, at every third digit. Writing that by hand with slicing is a small loop with an off-by-one in it, and it is already done.
- 02d pads, it does not truncate
The minor part must always show two digits, so 5 pence is '05' and not '5'. Without the padding, 1205 formats as '12.5' — which reads as twelve pounds fifty and is out by forty-five pence.
Split the amount into whole units and the remainder. Try it with a negative number first — the answer will tell you what to do before you split.
Work out the sign, then use divmod on abs(cents) so the two pieces are both positive. Put the minus back as a string when you build the result.
sign = '-' if cents < 0 else ''. units, minor = divmod(abs(cents), 100). Then one f-string: the sign, the units with a comma spec, a dot, and the minor part padded to two digits.
def format_money(cents): sign = '____' if cents < 0 else '' units, minor = divmod(____(cents), 100) return f'{sign}{units:____}.{minor:____}'
def format_money(cents):
sign = "-" if cents < 0 else ""
units, minor = divmod(abs(cents), 100)
return f"{sign}{units:,}.{minor:02d}"
Doing the arithmetic on a positive number and reattaching the sign as text sidesteps floor division entirely. It is a general move: when a calculation behaves differently either side of zero, normalise to one side, compute, and restore. Three of the cases here are negative because this is the only part of the function that can be wrong.
cents / 100 would be shorter and would put the value back into binary floating point at the last moment — after all the care taken to keep it out. For 1234 that happens to round back correctly; for a long enough column of them it will not. Integer arithmetic all the way to the string is the point.
:, is presentation for humans and :02d is a correctness requirement — dropping the first makes the number harder to read, dropping the second makes it wrong by a factor of ten. Worth being able to say which of your format specs are cosmetic and which are load-bearing.
This is British and American formatting. Much of Europe writes 1.234.567,89 and several conventions use a space as the group separator, so anything user-facing outside one locale should go through babel.numbers.format_currency or the locale module, which also know where the currency symbol goes — before the number in English, after it in French. Note too that two decimal places is not universal: the Japanese yen has none and the Kuwaiti dinar has three, so a real system stores the exponent per currency rather than assuming 100.
- Add a currency parameter that places the symbol correctly, then look up where the euro sign goes in French and in Irish English.
- Support currencies with a different number of minor digits by passing the exponent in, and check what that does to the divmod.
- Format negatives in accounting style — (12.34) rather than -12.34 — behind an option, and decide which should be the default for a balance sheet.