1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

10進数浮動小数点 Decimal 型 ⟺ DPD (固定ビット長バイナリ)変換

Last updated at Posted at 2022-02-26

目的

バイナリ データに「固定ビット長の 10 進数浮動小数点データ」を用いたいとき、IEEE 754 にある decimalN 形式(以下、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')

参考

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?