@@ -615,9 +615,10 @@ def __init__(
615615
616616 The `name` argument to this function would be `my_param`.
617617 :scale:
618- The maximum precision (total number of digits) of the number between 1 and 38.
618+ The number of digits to the right of the decimal point. If the value itself
619+ has more fractional digits, the value's scale is used so no digits are lost.
619620 :precision:
620- The number of digits to the right of the decimal point .
621+ The maximum precision (total number of digits) of the number between 1 and 38 .
621622 """
622623 self .value : decimal .Decimal = value
623624 self .name = name
@@ -638,11 +639,37 @@ def valid_scale_and_precision(self):
638639 return False
639640
640641 def _cast_expr (self ):
641- if self .scale and self .precision :
642- return self .CAST_EXPR .format (self .scale , self .precision )
642+ if self .scale is not None and self .precision is not None :
643+ precision , scale = self ._effective_precision_and_scale (self .precision , self .scale )
644+ # SQL DECIMAL type syntax is DECIMAL(precision, scale)
645+ return self .CAST_EXPR .format (precision , scale )
643646 else :
644647 return self .calculate_decimal_cast_string (self .value )
645648
649+ MAX_PRECISION = 38
650+
651+ def _effective_precision_and_scale (self , precision : int , scale : int ):
652+ """Widen a declared DECIMAL(precision, scale) so the bound value never loses
653+ fractional digits: the value's own scale is authoritative over an
654+ under-declared target scale. Integer-digit room from the declared type is kept.
655+ """
656+ try :
657+ sign , digits , exponent = self .value .as_tuple ()
658+ except AttributeError :
659+ return precision , scale
660+ if not isinstance (exponent , int ):
661+ # NaN / Infinity: leave the declared type as-is
662+ return precision , scale
663+
664+ value_scale = max (- exponent , 0 )
665+ value_int_digits = max (len (digits ) + exponent , 0 )
666+
667+ effective_scale = max (scale , value_scale )
668+ int_digits = max (precision - scale , value_int_digits )
669+ effective_precision = min (int_digits + effective_scale , self .MAX_PRECISION )
670+ effective_scale = min (effective_scale , effective_precision )
671+ return effective_precision , effective_scale
672+
646673 def calculate_decimal_cast_string (self , input : decimal .Decimal ) -> str :
647674 """Returns the smallest SQL cast argument that can contain the passed decimal
648675
0 commit comments