The Problem
Your invoice total is off by $0.01. A tax calculation shows $13.49 instead of $13.50. A unit price of $9.99 displays as $10.00. Or a stock move fails with Rounding error: 0.000000001 is not zero. These decimal precision issues cause real business problems — accounting discrepancies, failed bank reconciliations, and unhappy customers.
Common Symptoms
- Invoice totals off by 1 cent
- Tax amounts not matching expected values
- Quantity comparisons failing (
10.0 != 10.000000001) - Price unit showing wrong decimal places
- Stock moves stuck due to rounding validation
- Multi-currency conversions producing wrong amounts
How Odoo Handles Decimal Precision
Odoo uses the decimal.precision model to define rounding rules for different use cases:
# Built-in precision categories:
# 'Product Price' -> typically 2 decimal places
# 'Discount' -> typically 2 decimal places
# 'Product Unit of Measure' -> typically 3 decimal places
# 'Account' -> follows currency rounding
# Defined in code as:
price = fields.Float(digits='Product Price') # uses decimal.precision
amount = fields.Monetary(currency_field='currency_id') # uses currency roundingRoot Causes and Fixes
1. Wrong Decimal Precision Setting
The most common cause. The default precision for "Product Price" is 2 decimal places, but your business needs 4 (e.g., commodity pricing).
Fix: Go to Settings > Technical > Database Structure > Decimal Accuracy. Find the relevant category and change the number of digits:
- Product Price: increase to 4 for commodity pricing
- Product Unit of Measure: increase to 5 for high-precision quantities
- Discount: increase to 4 for fractional discounts
After changing, restart Odoo and update affected modules.
2. Floating-Point Comparison Bugs
Python floating-point arithmetic is inherently imprecise. Never compare floats with ==:
# BAD: floating-point comparison
if order.amount_total == expected_total: # may fail!
confirm_order()
# GOOD: use Odoo's float_compare
from odoo.tools import float_compare
if float_compare(order.amount_total, expected_total, precision_digits=2) == 0:
confirm_order()
# float_compare returns:
# -1 if a < b
# 0 if a == b (within precision)
# 1 if a > b3. Tax Rounding Method
Odoo supports two tax rounding methods that give different results:
- Round per Line: tax is rounded on each invoice line, then summed
- Round Globally: taxes are summed first, then rounded once
These can produce different totals. If your totals do not match your tax authority's expectations, switch the method.
Fix: Settings > Accounting > Taxes > Tax Rounding Method. Change between "Round per Line" and "Round Globally".
4. Currency Rounding Mismatch
Each currency in Odoo has a rounding precision. If it is wrong, all monetary calculations for that currency will be off.
Fix: Go to Accounting > Configuration > Currencies. Check the Rounding Factor:
- USD, EUR, GBP:
0.01(2 decimal places) - JPY, KRW:
1(no decimal places) - BHD, KWD:
0.001(3 decimal places)
5. Stored Computed Field Rounding
A computed field that stores a rounded value can accumulate errors when summed:
# BAD: rounding each line then summing
@api.depends('line_ids.price_unit', 'line_ids.quantity')
def _compute_total(self):
for rec in self:
rec.total = sum(
round(line.price_unit * line.quantity, 2)
for line in rec.line_ids
)
# BETTER: sum first, round once
@api.depends('line_ids.price_unit', 'line_ids.quantity')
def _compute_total(self):
for rec in self:
raw_total = sum(
line.price_unit * line.quantity
for line in rec.line_ids
)
rec.total = rec.currency_id.round(raw_total)6. Import Data Rounding
When importing data from CSV or Excel, values may have been pre-rounded differently than Odoo expects.
Fix: Ensure imported amounts use full precision. Let Odoo handle rounding according to its precision settings.
Debugging Rounding Issues
# Check decimal precision settings:
SELECT name, digits FROM decimal_precision;
# Check currency rounding:
SELECT name, rounding FROM res_currency WHERE active = true;
# In Odoo shell, test rounding:
from odoo.tools import float_round, float_compare
float_round(10.555, precision_digits=2) # 10.56
float_compare(10.00, 10.004, precision_digits=2) # 0 (equal)
# Check a specific computation:
invoice = env['account.move'].browse(123)
for line in invoice.invoice_line_ids:
print(f"{line.name}: qty={line.quantity} price={line.price_unit} "
f"subtotal={line.price_subtotal} tax={line.price_total - line.price_subtotal}")Best Practices
- Always use
float_compare()andfloat_round()fromodoo.tools - Use
fields.Monetaryfor money amounts — it respects currency rounding - Use
fields.Float(digits='Category')for non-monetary decimals - Set decimal precision before importing data
- Test with edge cases: $0.005 tax on 3 items, 1/3 quantities, multi-currency
- Use
currency_id.round()for monetary rounding in code