#33232 closed Bug (invalid)
DecimalField rounding error when upgrading from 3.1 to 3.2 on Postgres
| Reported by: | Michał Szczepańczyk | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 3.2 |
| Severity: | Normal | Keywords: | decimal, decimalfield, rounding, postgres |
| Cc: | Simon Charette | Triage Stage: | Unreviewed |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Example:
from django.db import models
class Foo(models.Model):
amount = models.DecimalField(decimal_places=2, max_digits=12)
from myapp.models import Foo
f = Foo(amount='1.125')
f.save()
f.refresh_from_db()
# the issue is here
f.amount # returns Decimal('1.12') in 3.1 and Decimal('1.13') in 3.2
Executed query for 3.2:
INSERT INTO "myapp_foo" ("amount") VALUES (1.125) RETURNING "myapp_foo"."id"
Executed query for 3.1:
INSERT INTO "myapp_foo" ("amount") VALUES ('1.12') RETURNING "myapp_foo"."id"
Issue was reproduced only when using postgres database.
Change History (6)
comment:1 by , 4 years ago
| Summary: | DecimalField rounding error when upgrading from 3.1 to 3.2 → DecimalField rounding error when upgrading from 3.1 to 3.2 on Postgres |
|---|
comment:2 by , 4 years ago
| Cc: | added |
|---|---|
| Resolution: | → invalid |
| Status: | new → closed |
follow-up: 4 comment:3 by , 4 years ago
That's for the response. In such case I think it would be good to mention the change in release docs.
Another consequence of this change is that we have different results between sqlite and postgres. When using sqlite the following query is generated:
INSERT INTO "myapp_foo" ("amount") VALUES ('1.12')
comment:4 by , 4 years ago
Replying to Michał Szczepańczyk:
That's for the response. In such case I think it would be good to mention the change in release docs.
I will try to figure out how to mention it in docs.
Another consequence of this change is that we have different results between sqlite and postgres. When using sqlite the following query is generated:
INSERT INTO "myapp_foo" ("amount") VALUES ('1.12')
Yes but it returns 1.13 as in MySQL and Oracle.
comment:5 by , 4 years ago
SQLite is also not a stellar example of ambiguous data type handling as detailed in their documentation. Since it doesn't have a proper type to store decimal data the rounding happens on the Django side.
comment:6 by , 4 years ago
The problem is that right now if I just want to round down the value I have to get the precision of the field from the model internals, which is a bit ugly I think. And also this has to be done everywhere where this field is updated. Why can't we keep everything that django.db.backends.utils.format_number does, but the last line that converts the value to string? There's a lot of flexibility that Python Decimal provides that's not available in SQL.
The code I had before:
with localcontext() as ctx:
ctx.rounding = ROUND_DOWN
self.items.update(
split=Decimal("100") * split / self.number_of_items
)
The code I have to have now:
with localcontext() as ctx:
value = Decimal("100") * split / number_of_items
ctx.rounding = ROUND_DOWN
max_digits = Model.split.field.max_digits
decimal_places = Model.split.field.decimal_places
if max_digits is not None:
ctx.prec = max_digits
if decimal_places is not None:
value = value.quantize(Decimal(1).scaleb(-decimal_places))
self.items.update(
split=value
)
This behavior was changed in 9c5c9bd7091b60fdccc405890dc4f44a8010e954 however I believe that the current one is correct, because we longer cast
Decimalsto strings. If you need to round differently I recommend to do so before passing values to the field.