/usr/lib/python3/dist-packages/postgresql/types/io/stdlib_decimal.py is in python3-postgresql 1.1.0-1build1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | ##
# types.io.stdlib_decimal
#
# I/O routines for transforming NUMERIC to and from decimal.Decimal.
##
from decimal import Decimal
from operator import itemgetter, mul
# You know it's gonna get serious :)
from itertools import chain, starmap, repeat, groupby, cycle, islice
from ...types import NUMERICOID
from . import lib
oid_to_type = {
NUMERICOID: Decimal,
}
##
# numeric is represented using:
# 1. ndigits, the number of *numeric* digits.
# 2. weight, the *numeric* digits "left" of the decimal point
# 3. sign, negativity. see `numeric_signs` below
# 4. dscale, *display* precision. used to identify exponent.
#
# NOTE: A numeric digit is actually four digits in the representation.
#
# Python's Decimal consists of:
# 1. sign, negativity.
# 2. digits, sequence of int()'s
# 3. exponent, digits that fall to the right of the decimal point
numeric_negative = 16384
def numeric_pack(x,
numeric_digit_length : "number of decimal digits in a numeric digit" = 4,
get0 = itemgetter(0),
get1 = itemgetter(1),
Decimal = Decimal,
pack = lib.numeric_pack
):
if not isinstance(x, Decimal):
x = Decimal(x)
x = x.as_tuple()
if x.exponent == 'F':
raise ValueError("numeric does not support infinite values")
# normalize trailing zeros (truncate em')
# this is important in order to get the weight and padding correct
# and to avoid packing superfluous data which will make pg angry.
trailing_zeros = 0
weight = 0
if x.exponent < 0:
# only attempt to truncate if there are digits after the point,
##
for i in range(-1, max(-len(x.digits), x.exponent)-1, -1):
if x.digits[i] != 0:
break
trailing_zeros += 1
# truncate trailing zeros right of the decimal point
# this *is* the case as exponent < 0.
if trailing_zeros:
digits = x.digits[:-trailing_zeros]
else:
digits = x.digits
# the entire exponent is just trailing zeros(zero-weight).
rdigits = -(x.exponent + trailing_zeros)
ldigits = len(digits) - rdigits
rpad = rdigits % numeric_digit_length
if rpad:
rpad = numeric_digit_length - rpad
else:
# Need the weight to be divisible by four,
# so append zeros onto digits until it is.
r = (x.exponent % numeric_digit_length)
if x.exponent and r:
digits = x.digits + ((0,) * r)
weight = (x.exponent - r)
else:
digits = x.digits
weight = x.exponent
# The exponent is not evenly divisible by four, so
# the weight can't simple be x.exponent as it doesn't
# match the size of the numeric digit.
ldigits = len(digits)
# no fractional quantity.
rdigits = 0
rpad = 0
lpad = ldigits % numeric_digit_length
if lpad:
lpad = numeric_digit_length - lpad
weight += (ldigits + lpad)
digit_groups = map(
get1,
groupby(
zip(
# group by NUMERIC digit size,
# every four digits make up a NUMERIC digit
cycle((0,) * numeric_digit_length + (1,) * numeric_digit_length),
# multiply each digit appropriately
# for the eventual sum() into a NUMERIC digit
starmap(
mul,
zip(
# pad with leading zeros to make
# the cardinality of the digit sequence
# to be evenly divisible by four,
# the NUMERIC digit size.
chain(
repeat(0, lpad),
digits,
repeat(0, rpad),
),
cycle([10**x for x in range(numeric_digit_length-1, -1, -1)]),
)
),
),
get0,
),
)
return pack((
(
(ldigits + rdigits + lpad + rpad) // numeric_digit_length, # ndigits
(weight // numeric_digit_length) - 1, # NUMERIC weight
numeric_negative if x.sign == 1 else x.sign, # sign
- x.exponent if x.exponent < 0 else 0, # dscale
),
list(map(sum, ([get1(y) for y in x] for x in digit_groups))),
))
def numeric_convert_digits(d, str = str, int = int):
i = iter(d)
for x in str(next(i)):
# no leading zeros
yield int(x)
# leading digit should not include zeros
for y in i:
for x in str(y).rjust(4, '0'):
yield int(x)
numeric_signs = {
numeric_negative : 1,
}
def numeric_unpack(x, unpack = lib.numeric_unpack):
header, digits = unpack(x)
npad = (header[3] - ((header[0] - (header[1] + 1)) * 4))
return Decimal((
numeric_signs.get(header[2], header[2]),
tuple(chain(
numeric_convert_digits(digits),
(0,) * npad
) if npad >= 0 else list(
numeric_convert_digits(digits)
)[:npad]),
-header[3]
))
oid_to_io = {
NUMERICOID : (numeric_pack, numeric_unpack, Decimal),
}
|