Resources / Engineering

Handling money in software: floats, paise and rounding

Why floating-point numbers break money calculations, how to store amounts safely, and how to split and round without losing a paisa.

By the Jinhub Technologies team · Updated October 2026 · 3 min read

The classic bug

In Python, JavaScript and most other languages, 0.1 + 0.2 does not equal 0.3. It equals 0.30000000000000004. The reason is that standard floating-point numbers are stored in binary, and most decimal fractions, including 0.1, have no exact binary representation, just as 1/3 has no exact decimal one.

For scientific calculations this tiny error is usually harmless. For money it is not. Errors accumulate across thousands of transactions, comparisons such as 'is the balance zero?' fail unexpectedly, and totals stop matching statements by a paisa here and there, which is exactly the kind of discrepancy that destroys trust in a financial product.

Option 1: store integers in the smallest unit

The simplest robust approach is to store every amount as a whole number of the smallest currency unit. ₹149.50 becomes 14950 paise. Integer arithmetic is exact, fast, and supported by every database and language.

Many payment gateways use the same convention in their APIs, expecting amounts in the smallest currency unit. Converting to rupees only happens at the edge, when displaying a value to a person.

Things to watch:

  • Not every currency has two decimal places, so if you handle multiple currencies, store the currency code alongside the amount and look up its precision.
  • Make sure the integer type is large enough for your largest totals.

Option 2: use a decimal type

Databases provide exact decimal columns (NUMERIC or DECIMAL in PostgreSQL and MySQL), and languages provide decimal libraries, such as Python's decimal module, which Django's DecimalField uses. Decimals are convenient when you need fractional units, such as interest rates or prices per unit with more than two decimal places.

Two rules make decimals safe: create them from strings, not floats (Decimal('0.1'), not Decimal(0.1), which inherits the float's error), and set the precision and rounding mode explicitly rather than relying on defaults.

Round deliberately, and in one place

Rounding is a business decision, not just a technical one. Decide when it happens, for example per line item or only on the final total, and which rule applies:

  • Round half up: 2.5 becomes 3. Familiar to most people and common on invoices.
  • Round half to even (banker's rounding): 2.5 becomes 2 and 3.5 becomes 4. It avoids a systematic upward bias across many values. Python's built-in round() and the decimal module's default context both use this rule, which surprises many developers.

Whatever you choose, put the rounding logic in one shared function and use it everywhere. Rounding in several places with slightly different rules is a common source of totals that do not add up.

Splitting amounts without losing a paisa

Dividing ₹100 equally between three people gives ₹33.33 each, which adds up to ₹99.99. The missing paisa has to go somewhere, or the books will not balance.

The standard technique is allocation:

  • Work in paise: 10000 divided by 3 is 3333 with a remainder of 1.
  • Give every share the base amount of 3333 paise.
  • Distribute the remainder one paisa at a time to the first shares, giving 3334, 3333 and 3333.

The shares now always sum to the original amount. The same approach works for splitting by percentages, such as allocating a payment across several invoices.

Test the edge cases

Money bugs hide in edges, so test them explicitly: zero amounts, refunds, values that round exactly at the halfway point, very large totals, and splits that do not divide evenly. A useful property test is that the sum of the parts always equals the whole, whatever input you try.

These rules underpin the fintech design principles we follow across our products. Get storage and rounding right at the start and a whole category of hard-to-trace problems never appears.