Why can't you use floating point numbers for prices and balances?
Because binary floating point cannot represent most decimal fractions exactly. A value like 0.1 is stored as a close approximation, and those approximations accumulate across arithmetic until a computed balance is no longer the balance. In ordinary software that is a rounding curiosity. In a price path it is money.
System architecture · Arbitrage Engine
What actually goes wrong
Floating point stores numbers in binary. One tenth in binary is a repeating fraction, exactly as one third is in decimal, so it cannot be stored exactly in a finite number of bits. What gets stored is the nearest representable value.
This is why adding 0.1 and 0.2 does not give exactly 0.3 in most languages. The discrepancy is tiny and completely irrelevant until you perform thousands of operations on it, at which point the error is no longer tiny and no longer predictable.
Why it survives testing
Because small cases look correct. A handful of operations produces an error far below any threshold you would notice, and a test asserting a value to two decimal places passes comfortably.
The error appears at scale, across long-running accumulation, and it appears as a discrepancy rather than a failure. Nothing throws. A balance is simply slightly wrong, and reconciliation finds it weeks later when nobody can reconstruct which operations caused it.
What to use instead
Either a decimal type that stores base-ten digits exactly, or integers in the smallest unit, holding cents or satoshis rather than fractional currency. Both are exact; which is better depends on the language and the precision you need.
Integers are appealing because they are fast and unambiguous, but they push the decimal point into your head, and mistakes there are as costly as the problem you were avoiding. A proper decimal type is usually the safer default.
Where the boundary is
The price path. Anything that computes a spread, sizes a position, or moves a balance should be decimal end to end, with no float anywhere in it.
Outside that, floats are fine and often preferable. Charting, analytics, machine learning features, anything statistical. The distinction is whether the number is money or a measurement of money, and it is worth making that boundary explicit in the code rather than assumed.
How we have used it
Our cross-exchange arbitrage engine is written in Go with decimal arithmetic throughout the price path and no floating point in it at all. On a system computing spreads across venues and sizing trades against them, a drifting representation is not a rounding concern, it is a direct cost.
It sits alongside the engine's other correctness measures: sequence-gap detection on the order book, explicit partial-fill recovery, and circuit breakers on loss, latency and error rate.
COMMON QUESTIONS /
Questions people also ask
Is 0.1 plus 0.2 really not 0.3?
In binary floating point, no. Both values are stored as approximations because neither has an exact binary representation, and their sum is a value very slightly above 0.3. The difference is negligible once and meaningful after enough arithmetic.
Should you store money as integers or decimals?
Either works if applied consistently. Integers in minor units are fast and exact but move the decimal point into the developer's head. A decimal type is exact and self-describing, which usually makes it the safer default unless performance demands otherwise.
Is JavaScript safe for financial calculations?
Not with plain numbers, which are IEEE-754 doubles and carry exactly this problem. Use a decimal library or BigInt in minor units. The risk is higher in JavaScript than in languages with a built-in decimal type, precisely because the default looks like it works.
Do rounding errors matter if you round at the end?
Only if the error stayed below half your rounding unit, and across enough accumulated operations it will not. Rounding at the end hides small errors and does nothing about compounded ones, which are the kind that actually appear in production.