目的
バイナリ データに「固定ビット長の 10 進数浮動小数点データ」を用いたいとき、IEEE 754 にある decimal N 形式(以下、DPD)は一つの選択肢になります。そこで、decimal モジュールの Decimal 型を DPD 形式に変換/逆変換するものが欲しくなります。
プログラム
変換は Decimal 型と int 型の間で行います。
Python3
from decimal import Decimal
class DPD:
COMBINATION_DECODE_TABLE = tuple(
# 0=NORMAL, 1=INFINITY, 2=NaN
((0, ((g >> 3) & 3), (g & 7)),
(0, ((g >> 1) & 3), ((g & 1) | 8)),
(1, 0, 0),
(2, 0, 0))
[(2 + (g & 1)) if g >= 30 else (1 if g >= 24 else 0)]
for g in range(32))
COMBINATION_ENCODE_TABLE = tuple(
([((e << 3) | d) for d in range(8)] +
[((e << 1) | d) for d in (0x18, 0x19)])
for e in range(3))
DPD_DECODE_TABLE = tuple(
'%03x' % (lambda x: ((
x,
(x | 0x0ee) - 0x06e + ((x >> 4) & 0x06),
(x | 0xe0e) - 0x60e + ((x >> 8) & 0x06),
(x | 0xeee) - 0x66e + ((x >> 8) & 0x06),
(x | 0xeee) - 0x6e6 + ((x >> 4) & 0x60),
(x | 0x0ee) - 0x066,
(x | 0xeee) - 0x666,
)[max(0, ((p >> 1) & 7) - 4) + (0 if (~p & 14) else ((p >> 5) & 3))]))
(p + (p & 0x380))
for p in range(1024))
DPD_ENCODE_TABLE = tuple(
(lambda x: (
0x000 | (x & 0x077) | ((x & 0x700) >> 1),
0x008 | (x & 0x071) | ((x & 0x700) >> 1),
0x00a | (x & 0x011) | ((x & 0x700) >> 1) | ((x & 0x006) << 4),
0x00c | (x & 0x071) | ((x & 0x100) >> 1) | ((x & 0x006) << 7),
0x00e | (x & 0x011) | ((x & 0x100) >> 1) | ((x & 0x006) << 7),
0x02e | (x & 0x011) | ((x & 0x100) >> 1) | ((x & 0x060) << 3),
0x04e | (x & 0x011) | ((x & 0x700) >> 1),
0x06e | (x & 0x011) | ((x & 0x100) >> 1),
)[(0, 6, 2, 8, 5, 3, 7, 4, 1)[(x & 0x888) % 9]])
(int('%03d' % d, 16))
for d in range(1000))
def __init__(self, bits=64):
if (bits & 31):
raise NotImplementedError
S = DPD
self.S = S
self.decomb = S.COMBINATION_DECODE_TABLE
self.encomb = S.COMBINATION_ENCODE_TABLE
self.dedpd = S.DPD_DECODE_TABLE
self.endpd = S.DPD_ENCODE_TABLE
self.k = bits
self.w = (self.k >> 4) + 4
self.t = self.k - (1 + 5 + self.w)
self.p = (self.k >> 5) * 9 - 2
self.emax = 3 << ((self.k >> 4) + 3)
self.bias = self.emax + self.p - 2
self.p_sign = self.k - 1
self.p_mode = self.k - (1 + 5)
self.p_nan = self.p_mode - 1
self.p_exp = self.t
self.p_trailings = tuple(reversed([p for p in range(0, self.t, 10)]))
self.b_emax = self.emax << 1
self.n_expt = self.p_mode - self.p_exp
self.m_expt = (1 << self.n_expt) - 1
self.n_dpd = int(self.t / 10)
self.n_dec = self.n_dpd * 3
self.sign = 1 << self.p_sign
self.combination = self.sign - (1 << self.p_exp)
self.trailing = (1 << self.p_exp) - 1
self.infinite = 0x1E << self.p_mode
self.qnan = 0x3E << self.p_nan
self.snan = 0x3F << self.p_nan
def decode(self, binary, context=None):
s = ('', '-')[(binary >> self.p_sign) & 1]
(mode, eh, d1) = self.decomb[(binary >> self.p_mode) & 0x1f]
if mode == 1:
return Decimal(s + 'Infinity', context)
if mode == 2:
return Decimal(s + ('', 's')[(binary >> self.p_nan) & 1] + 'NaN', context)
if d1 == 0 and (binary & self.trailing) == 0:
return Decimal(s + '0', context)
e = ((eh << self.n_expt) | ((binary >> self.p_exp) & self.m_expt)) - self.bias
d = ('%d' % d1)
for p in self.p_trailings:
d += self.dedpd[(binary >> p) & 0x3ff]
while len(d) and d[0] == '0':
d = d[1:]
while len(d) and d[-1] == '0' and e < 0:
d = d[:-1]
e += 1
return Decimal('%s%se%d' % (s, d, e), context)
def encode(self, decimal):
if type(decimal) != Decimal:
raise NotImplementedError
(s, d, e) = decimal.as_tuple()
rs = s << self.p_sign
if decimal.is_zero():
return rs
if decimal.is_infinite():
return (rs | self.infinite)
if decimal.is_qnan():
return (rs | self.qnan)
if decimal.is_snan():
return (rs | self.snan)
dp = len(d) - self.p
e += dp
if dp < 0:
d = d + ((0,) * (-dp))
d = d[:self.p]
e += self.bias
if e >= self.b_emax:
return (rs | self.infinite)
if e <= -self.p:
return rs
if e < 0:
d = (((0,) * (-e)) + d)[:self.p]
e = 0
m = self.encomb[e >> self.n_expt][d[0]]
re = (m << self.p_mode) | ((e & self.m_expt) << self.p_exp)
rd = 0
for i in range(1, self.p, 3):
rd = (rd << 10) | self.endpd[d[i] * 100 + d[i + 1] * 10 + d[i + 2]]
return (rs | re | rd)
テスト
converter = DPD(32) # 32 = バイナリのビット長(32の倍数のみ)
enc = converter.encode(Decimal('0.1')) # = 0x25e00000
dec = converter.decode(enc) # = Decimal('0.1')
print(hex(enc), dec.__repr__()) # 0x25e00000 Decimal('0.1')
参考