-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathstdlib_decimal.py
More file actions
161 lines (150 loc) · 4.27 KB
/
stdlib_decimal.py
File metadata and controls
161 lines (150 loc) · 4.27 KB
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]),
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),
}