From 91e58dcd1b438120e3268f8fa5864bdb1ff3a81f Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 17 Sep 2026 10:53:38 +0200 Subject: [PATCH] Fix precision issue --- src/pretix/base/templatetags/money.py | 13 +++++++++++++ src/tests/base/test_templatetag.py | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/src/pretix/base/templatetags/money.py b/src/pretix/base/templatetags/money.py index 477943d433..91880ac7e5 100644 --- a/src/pretix/base/templatetags/money.py +++ b/src/pretix/base/templatetags/money.py @@ -49,6 +49,13 @@ def money_filter(value: Optional[Decimal | float | int | str], arg='', hide_curr raise ValueError("No currency passed.") arg = arg.upper() + if value.normalize().as_tuple().exponent < -9: + # Heuristic: It's unlikely we'll ever see values of less than 0.000000001 in any currency. Therefore, if we + # do see them, we very likely deal with a floating point error. This happens mostly in dev mode when computations + # are made in SQLite, which uses REAL precision, but it can also happen when we naively pass a float from Python + # land to this filter (even though it should not happen). + value = value.quantize(Decimal('1e-9'), ROUND_HALF_UP).normalize() + currency_places = settings.CURRENCY_PLACES.get(arg, 2) required_places = -value.normalize().as_tuple().exponent render_places = max(currency_places, required_places) @@ -108,6 +115,12 @@ def tax_rate_format(number: Optional[Decimal | float | int | str]): number = Decimal('0.00') if not isinstance(number, Decimal): raise TypeError("Invalid data type passed to tax rate format filter: %r" % type(number)) + if number.normalize().as_tuple().exponent < -9: + # Heuristic: It's unlikely we'll ever see values of less than 0.000000001 in any currency. Therefore, if we + # do see them, we very likely deal with a floating point error. This happens mostly in dev mode when computations + # are made in SQLite, which uses REAL precision, but it can also happen when we naively pass a float from Python + # land to this filter (even though it should not happen). + number = number.quantize(Decimal('1e-9'), ROUND_HALF_UP).normalize() return mark_safe( formats.number_format( number, diff --git a/src/tests/base/test_templatetag.py b/src/tests/base/test_templatetag.py index a4b5cd3ef0..c7e71bb869 100644 --- a/src/tests/base/test_templatetag.py +++ b/src/tests/base/test_templatetag.py @@ -60,7 +60,9 @@ def test_urlreplace_replace_parameter(): "locale,amount,currency,expected", [ ("en", None, "USD", "$0.00"), + ("en", "", "USD", ""), ("en", 1000000, "USD", "$1,000,000.00"), + ("en", 2.23, "USD", "$2.23"), ("en", Decimal("1000.00"), "USD", "$1,000.00"), ("de", Decimal("1.23"), "EUR", "1,23" + NBSP + "€"), ("de", Decimal("1000.00"), "EUR", "1.000,00" + NBSP + "€"), @@ -74,6 +76,7 @@ def test_urlreplace_replace_parameter(): # deal with precision that is higher than the currency ("de", Decimal("1.234"), "EUR", "1,234" + NBSP + "€"), + ("de", 1.234, "EUR", "1,234" + NBSP + "€"), ("de", Decimal("1.2340"), "EUR", "1,234" + NBSP + "€"), ("de", Decimal("1.2300"), "EUR", "1,23" + NBSP + "€"), ("de", Decimal("1023.1"), "JPY", "1.023,10" + NBSP + "¥"), @@ -118,6 +121,11 @@ def test_money_filter_hidecurrency(locale, amount, currency, expected): ("en", Decimal("2.50"), "2.5"), ("en", Decimal("4.3e7"), "43000000"), ("en", Decimal("2.2340"), "2.234"), + ("en", "2.23", "2.23"), + ("en", 2.23, "2.23"), + ("en", 2, "2"), + ("en", "", ""), + ("en", None, "0"), ] ) def test_tax_rate_format(locale, rate, expected):