Opened 3 years ago

Closed 3 years ago

Last modified 3 years ago

#32555 closed Bug (duplicate)

DecimalField Rounding inconsistency (PostgreSQL)

Reported by: bluppfisk Owned by: nobody
Component: Database layer (models, ORM) Version: 2.2
Severity: Normal Keywords: decimalfield, rounding, float
Cc: bluppfisk Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Note, I've initially posted this on stackoverflow

Django 2.2, PostgreSQL database. I have a Line object with a positions property. This is an ArrayField of a DecimalField with a max_digits of 12 and decimal_places of 3.

I store some floats as Decimals and get them back out of the Database:

pos = [6586.87849502, 2.04190477e-01, 7.14666669e-01]
line = Line(positions=pos)
line.save()
line.refresh_from_db()  # send it through Django's ORM piping
print(line.positions)

Output:

[Decimal('6586.879'), Decimal('0.204'), Decimal('0.715')]

Interestingly, the first position was rounded up despite its next more significant digit being below 5. The other float is rounded down as expected.

I thought it might be PostgreSQL messing around, but no:

INSERT INTO line(positions) VALUES (array[6586.87849502, 2.04190477e-01, 7.14666669e-01]) RETURNING positions;

positions (numeric[])
{6586.878,0.204,0.715}

The real issue is that I should be able to predict what will come out of the database, down to the required precision of three decimal places:

[Decimal(x).quantize(Decimal("0.001")) for x in pos]

might yield

[Decimal('6586.878'), Decimal('0.204'), Decimal('0.715')]

or

[Decimal('6586.879'), Decimal('0.205'), Decimal('0.714')]

depending on the decimal.ROUND_* flag I pass in quantize(), but I never get consistent rounding throughout the array.

I also tried using the django.db.backends.util::number_format function for every of my Decimals as I gathered that this is used by Django before inserting into the database, but the results are still inconsistent.

Change History (3)

comment:1 by Mariusz Felisiak, 3 years ago

Resolution: duplicate
Status: newclosed

I cannot reproduce the incosistent rounding, however I believe we can mark this as a duplicate of #26459. Providing a custom context to DecimalField should fix this and similar issues, see also discussion and #28164.

comment:2 by bluppfisk, 3 years ago

I agree that is related, but even as I imitate Django's behaviour by using its own field and field configuration, the rounding is correct. It is only when I leave it fully up to Django that the rounding error happens.

def _normalize_positions(self):
        decimal_field = self._meta.get_field('positions').base_field
        self.positions = [
            decimal_field.context.create_decimal_from_float(val)
            .quantize(Decimal("1").scaleb(-decimal_field.decimal_places),
            context=decimal_field.context)
            for val in self.positions
        ]

[Decimal('6586.878'), Decimal('0.204'), Decimal('0.715')]

This is good.

What is Django doing differently than me? It would seem logical that Django takes care of the normalising in a reproducible way.

comment:3 by bluppfisk, 3 years ago

Cc: bluppfisk added
Note: See TracTickets for help on using tickets.
Back to Top