Repository: django/django

Issue:
# Fixed #36233 -- Avoided quantizing integers stored in DecimalField on SQLite.

#### Trac ticket number
<!-- Replace XXXXX with the corresponding Trac ticket number, or delete the line and write "N/A" if this is a trivial PR. -->

ticket-36233

#### Branch description
Fixed a crash in DecimalField when using SQLite with integers exceeding 15 digits (e.g., 16-digit IDs).

The Issue: SQLite lacks a native decimal type. When retrieving large integers (e.g., 9999999999999999), the default converter treats them as floating-point numbers. Since IEEE 754 floats only support ~15 significant digits, precision is lost (e.g., becoming 10000000000000000). When Django attempts to quantize() this inaccurate value against the field's context, it raises decimal.InvalidOperation.

The Fix: Updated django.db.backends.sqlite3.operations.py to check if the underlying SQLite driver returned a native int. If so, the value is converted directly to Decimal, bypassing the lossy float conversion entirely.

Verification: Added a regression test test_sqlite_integer_precision_bypass in model_fields/test_decimalfield.py to ensure 16-digit integers are stored and retrieved correctly without crashing.
#### Checklist
- [ ] This PR targets the `main` branch. <!-- Backports will be evaluated and done by mergers, when necessary. -->
- [ ] The commit message is written in past tense, mentions the ticket number, and ends with a period.
- [ ] I have checked the "Has patch" ticket flag in the Trac system.
- [ ] I have added or updated relevant tests.
- [ ] I have added or updated relevant docs, including release notes if applicable.
- [ ] I have attached screenshots in both light and dark modes for any UI changes.


Implementation patch:
diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py
index 23c17054d260..18ff204ae37b 100644
--- a/django/db/backends/sqlite3/operations.py
+++ b/django/db/backends/sqlite3/operations.py
@@ -300,10 +300,15 @@ def convert_timefield_value(self, value, expression, connection):
                 value = parse_time(value)
         return value
 
+    @staticmethod
+    def _create_decimal(value):
+        if isinstance(value, (int, str)):
+            return decimal.Decimal(value)
+        return decimal.Context(prec=15).create_decimal_from_float(value)
+
     def get_decimalfield_converter(self, expression):
         # SQLite stores only 15 significant digits. Digits coming from
         # float inaccuracy must be removed.
-        create_decimal = decimal.Context(prec=15).create_decimal_from_float
         if isinstance(expression, Col):
             quantize_value = decimal.Decimal(1).scaleb(
                 -expression.output_field.decimal_places
@@ -311,7 +316,7 @@ def get_decimalfield_converter(self, expression):
 
             def converter(value, expression, connection):
                 if value is not None:
-                    return create_decimal(value).quantize(
+                    return self._create_decimal(value).quantize(
                         quantize_value, context=expression.output_field.context
                     )
 
@@ -319,7 +324,7 @@ def converter(value, expression, connection):
 
             def converter(value, expression, connection):
                 if value is not None:
-                    return create_decimal(value)
+                    return self._create_decimal(value)
 
         return converter
 


Test patch:
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
index cdf3c00080c9..1d8a447dee8b 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -102,6 +102,7 @@ def get_choices():
 
 class BigD(models.Model):
     d = models.DecimalField(max_digits=32, decimal_places=30)
+    large_int = models.DecimalField(max_digits=16, decimal_places=0, null=True)
 
 
 class FloatModel(models.Model):
diff --git a/tests/model_fields/test_decimalfield.py b/tests/model_fields/test_decimalfield.py
index 17f59674e872..bab9a39c19d7 100644
--- a/tests/model_fields/test_decimalfield.py
+++ b/tests/model_fields/test_decimalfield.py
@@ -5,6 +5,7 @@
 from django.core import validators
 from django.core.exceptions import ValidationError
 from django.db import connection, models
+from django.db.models import Max
 from django.test import TestCase
 
 from .models import BigD, Foo
@@ -140,3 +141,20 @@ def test_roundtrip_with_trailing_zeros(self):
         obj = Foo.objects.create(a="bar", d=Decimal("8.320"))
         obj.refresh_from_db()
         self.assertEqual(obj.d.compare_total(Decimal("8.320")), Decimal("0"))
+
+    def test_large_integer_precision(self):
+        large_int_val = Decimal("9999999999999999")
+        obj = BigD.objects.create(large_int=large_int_val, d=Decimal("0"))
+        obj.refresh_from_db()
+        self.assertEqual(obj.large_int, large_int_val)
+
+    def test_large_integer_precision_aggregation(self):
+        large_int_val = Decimal("9999999999999999")
+        BigD.objects.create(large_int=large_int_val, d=Decimal("0"))
+        result = BigD.objects.aggregate(max_val=Max("large_int"))
+        self.assertEqual(result["max_val"], large_int_val)
+
+    def test_roundtrip_integer_with_trailing_zeros(self):
+        obj = Foo.objects.create(a="bar", d=Decimal("8"))
+        obj.refresh_from_db()
+        self.assertEqual(obj.d.compare_total(Decimal("8.000")), Decimal("0"))