LoginSignup
3
4

More than 3 years have passed since last update.

Python で疑似 n-bit int クラスを自作する

Last updated at Posted at 2021-05-01

Python で疑似 n-bit int クラスを自作してみた.

背景

Cracking the Coding Interview を読んでいて,Bit Manipulation の章で実際にコードを書くとき Python の int が扱いづらかった.
Python の int には値の大きさに制限がないので.それに起因して整数値を 01 の並びとして扱うときに,下にあるようにいろいろ不便があった.

例 1. 負数のバイナリ表記が見づらい

Python のバイナリ表記は [-]絶対値のバイナリ表記 がフォーマット.
簡単のため 8 bit の int 型を扱っていると想定.
0 は 8 bit で b00000000 なので ~0 を計算すると b11111111 となる.
これを Python で ~0 を計算して表示すると -0000001 となる.
扱う bit 数に制限がないのでこういうフォーマットになるのも当然なんだけど,今回自分が使いたい用途ではちょっと見づらい.

n = ~0
print(f"{n:08b}") # -0000001

例 2. 左シフトがトランケートされない

これも扱う bit 数に制限がないから当たり前なんだけど,左シフトしたときに扱いたい bit 数からはみ出た分がそのまま残る.
15 = b1111 を左に 2 だけシフトさせることを考える.
8 bit の int 型の場合,15 を左に 2 だけシフトさせると 15 << 2 = b1100 となり,8 bit 分からあふれた先頭の 11 がトランケートされる.
一方 Python の場合は 15 << 2 = b111100 のまま.
決まった bit 数での int の計算を想定している場合,都合が悪いことがある.

// C++
int8_t n = 15       // b1111
n <<= 2             // b1100
int8_t m = 1        // b0001
int8_t res = n | m  // b1101
# Python
n = 15      # b1111
n <<= 2     # b111100
m = 1       # b000001
res = n | m # b111101

Python にも int32 や int64 みたいなのがないか調べたけど,見当たらなかったので自作してみた.

実行環境

  • Python 3.8.5
  • Docker Container: Ubuntu 20.04

32 bit 型 (Int32) だけはテストした(ソースコードとテスト: https://github.com/e5pe0n/algorithm-training/tree/master/cracking_the_coding_interview_6th/chapter05/python/bitint

自作整数型クラス Int の設計

欲しい機能

  • 扱う bit 数を 32 bit や 64 bit に制限できる
    • << での先頭ビットのトランケート
  • 01 の並びが 2 の補数でアサインされたバイナリ表記を表示できる
    • 組込み関数 format()db などフォーマットを指定できる
  • Built-in の int と併用できる
    • +- といった算術演算子を int と組み合わせて適用できる
    • &| といったビット演算子を int と組み合わせて適用できる

Int の実装

コンストラクタ

コンストラクタ部分の全体

class Int:
    def __init__(self, value: Union[int, str], bit: int):
        if bit <= 0:
            raise ValueError("bit must be greater than 0.")

        self.__bit: int = bit
        self.__max: int = (1 << (self.__bit - 1)) - 1
        self.__min: int = -self.__max - 1

        if isinstance(value, int):
            # range check
            if value < self.min:
                raise ValueError(
                    f"underflow in {self.__bit}-bit integer value. \
                        value must be between [{self.__min}, {self.__max}]")
            elif self.max < value:
                raise ValueError(
                    f"overflow in {self.bit}-bit integer value. \
                        value must be between [{self.__min}, {self.__max}]")

            if value >= 0:
                self.__value: int = value
                self.__bin: int = self.__value
            else:
                self.__value: int = value
                self.__bin: int = self.__cmpl(abs(self.__value))

        elif isinstance(value, str):
            if len(value) > self.__bit:
                raise ValueError(f"length of value must be between [1, {self.__bit}].")

            if not pat.match(value):
                raise ValueError("value must be a sequence of 0 and 1.")

            if len(value) == self.__bit and value[0] == '1':
                # MSB is 1 so we must calculate the complement
                self.__bin: int = int(value, 2)
                self.__value: int = -self.__cmpl(self.__bin)
            else:
                self.__value: int = int(value, 2)
                self.__bin: int = self.__value

        else:
            raise TypeError("value must be an int or a str object.")

    @property
    def bit(self) -> int:
        return self.__bit

    @property
    def bin(self) -> int:
        return self.__bin

    @property
    def value(self) -> int:
        return self.__value

    @property
    def max(self) -> int:
        return self.__max

    @property
    def min(self) -> int:
        return self.__min

    def __cmpl(self, n: int) -> int:
        return ((self.__max + 1) << 1) - n

...

def Int16(value):
    return Int(value, 16)


def Int32(value):
    return Int(value, 32)


def Int64(value):
    return Int(value, 64)

int とバイナリ表記の str (value) から,与えられた bit 数 (bit) の Int オブジェクトを作れるようにする.
bit が 0 以下の場合は ValueError を投げる.
__max は与えられた bit 数で扱える最大値,__min は最小値.

例えば 4 bit の場合は __max = 1 << (4 - 1) - 1 = b1000 - 1 = 8 - 1 = 7__min = -7 - 1 = -8 になる.
0 を表す分だけ __max の方が絶対値が小さい.

positive integers negative integers
7 (b0111) -1 (b1111)
6 (b0110) -2 (b1110)
5 (b0101) -3 (b1101)
4 (b0100) -4 (b1100)
3 (b0011) -5 (b1011)
2 (b0010) -6 (b1010)
1 (b0001) -7 (b1001)
0 (b0000) -8 (b1000)

bit 数や最大値,最小値はオブジェクトの外部からも参照したいが代入されたくない.
ダンダー (Double Underscore: __) でマングリングして外部から隠し,代わりに @propety をつけてゲッターを用意しておく.

def __init__(self, value: Union[int, str], bit: int):
    if bit <= 0:
        raise ValueError("bit must be greater than 0.")

    self.__bit: int = bit
    self.__max: int = (1 << (self.__bit - 1)) - 1
    self.__min: int = -self.__max - 1
@property
def bit(self) -> int:
    return self.__bit

@property
def max(self) -> int:
    return self.__max

@property
def min(self) -> int:
    return self.__min

value が int の場合,value の値が bit 型整数値の最大値と最小値の範囲におさまっているかチェックする.

if isinstance(value, int):
    # range check
    if value < self.min:
        raise ValueError(
            f"underflow in {self.__bit}-bit integer value. \
                value must be between [{self.__min}, {self.__max}]")
    elif self.max < value:
        raise ValueError(
            f"overflow in {self.bit}-bit integer value. \
                value must be between [{self.__min}, {self.__max}]")

__bin はバイナリ表記用の整数値を保持する.
4-bit int で -5 を扱うことを考える.
Python で -5 をバイナリ表記すると -0101 となるが,2 の補数表現を使う場合は 1011 となる.
Int オブジェクトでは -5 をバイナリで表示する場合 1011 と表示したい.
-5 + 5 = 0 i.e. bxxxx + b0101 = b0000 から -5 = b1011 が得られる.
トランケートされた 1 bit を考慮すると b0xxxx + b00101 = b10000 i.e. b0xxxx = b10000 - b00101 = 2^4 - 5 = 11 (b01011) となり,b1011 の 10 進整数値は bit 数 4 と -5 の絶対値 5 から直接計算できることがわかる.
一般化して,__bin = 2**__bit - abs(__value).
補数を計算する補助関数 __cmpl() では計算しておいた最大値 __max に 1 を足して 2 倍することで 2**__bit を計算してる.

if value >= 0:
    self.__value: int = value
    self.__bin: int = self.__value
else:
    self.__value: int = value
    self.__bin: int = self.__cmpl(abs(self.__value))
def __cmpl(self, n: int) -> int:
    return ((self.__max + 1) << 1) - n

value が str の場合,文字数が bit 数以下であること,0 と 1 からなり,必ず 0 か 1 を含む文字列であることをチェックする.

import re


pat = re.compile(r"[01]+")

...

elif isinstance(value, str):
    if len(value) > self.__bit:
        raise ValueError(f"length of value must be between [1, {self.__bit}].")

    if not pat.match(value):
        raise ValueError("value must be a sequence of 0 and 1.")

文字数が bit 数と同じで先頭が 1 の場合は負数なので補数を計算する必要がある.
4-bit int で考える.
1011 はそのまま 10 進数にすると 11 だが,先頭が 1 なので負数,つまり補数表現だと 10 進数で x + (-x) = 0 となる何かしら 0 以上の整数 x にマイナスがついたものを表す.
b01011 + b0yyyy = b10000 から b0yyyy = b00101 = 5 なので 1011 は補数表現で -5 を表すことがわかる.
10 進数で考えると 5 = 2^4 - 11 なので,これにマイナスをつけると補数表現での値が得られる.
一般化して __value = -(2**__bit - __bin)
実装では補助関数 __cmpl() を使っている.

if len(value) == self.__bit and value[0] == '1':
    # MSB is 1 so we must calculate the complement
    self.__bin: int = int(value, 2)
    self.__value: int = -self.__cmpl(self.__bin)
else:
    self.__value: int = int(value, 2)
    self.__bin: int = self.__value

__value__bin とも外部から参照したいが値の変更はされたくないので,変数名をマングリングしてゲッターを作る.

@property
def bin(self) -> int:
    return self.__bin

@property
def value(self) -> int:
    return self.__value

16-bit,32-bit,64-bit はよく使うかと思って個別にオブジェクト作成用の関数を用意しといた.

def Int16(value):
    return Int(value, 16)


def Int32(value):
    return Int(value, 32)


def Int64(value):
    return Int(value, 64)

文字列用のスペシャルメソッドの実装

組込み関数の str()print() で表示される文字列を定義するため __str__() を実装する.
シンプルに整数値だけのほうが見やすい気がするので __str__() では Built-in の int と同じ表示にする.

n = Int32(5)
print(n)    # 5
def __str__(self) -> str:
    return str(self.value)

__str__() とは別に,デバッグ用に __repr__() を実装する.
どんなオブジェクトかがわかるように Int(bit=32, value=5, bin=5) のように表示する.

def __repr__(self) -> str:
    return f"Int(bit={self.__bit}, value={self.__value}, bin={self.__bin})"

フォーマット用のスペシャルメソッドとして __format__() を実装する.
これを定義しておくことで,Built-in の int と同じように f 文字列や format 関数で db などを指定して表示することができる.

n = Int32(5)
print(f"{n:05d}")   # 00005
print(f"{n:.5f}")   # 5.00000
print(f"{n:05b}")   # 00101
def __format__(self, format_spec: str) -> str:
    t = format_spec[-1]
    if t in ('b', 'o', 'x', 'X'):
        return format(self.bin, format_spec)
    else:
        return format(self.value, format_spec)

算術演算子用のスペシャルメソッドの実装

オブジェクトに演算子を適用できるようにスペシャルメソッドを実装する.
例えば __add__() を定義しておくと次のように + が使えるようになる.
実際,a + ba.__add__(b) を実行している.
__add__() の引数として自作の Int と int を扱えるようにしておけば,Int + IntInt + int が計算できる.

ope1 = Int32(1)
ope2 = Int32(2)
res = ope1 + ope2 # Int(3)

ope1 = Int32(1)
ope2 = 2
res = ope1 + 2  # Int(3)

補助関数 __validate_opes() で引数のバリデーションチェックを行う.
仕様として +- の結果はより大きい bit 数の Int オブジェクトを返すようにした(わかりやすさ,実装のしやすさから).

def __validate_opes(self, other: Union[int, Int]) -> Tuple[Int, Int]:
    if isinstance(other, int):
        return (self, Int(other, self.__bit))
    elif isinstance(other, Int):
        greater_bit = max(self.__bit, other.bit)
        return (Int(self.value, greater_bit), Int(other.value, greater_bit))
    else:
        raise TypeError("operands must be int or Int objects")

def __apply_num_op(self, ope1: Int, ope2: Int, op: Callable[[int, int], int]) -> Int:
    res = op(ope1.value, ope2.value)
    return Int(res, ope1.bit)

...

def __add__(self, other: Union[int, Int]) -> Int:
    ope1, ope2 = self.__validate_opes(other)
    return self.__apply_num_op(ope1, ope2, operator.add)

__add__() だけだと int + Int が計算できないので __radd__() を実装する.
a + b を計算するとき,a に __add__() が定義されていない場合は b の __radd__() が実行される(b に __radd__() も定義されてない場合は TypeError になる).
+ は交換法則によってオペランドの順序は計算結果に影響しないので,__radd__() ではそのまま __add__() を使っている.

ope1 = 1
ope2 = Int32(2)
res = ope1 + ope2 # Int(3)
def __radd__(self, other: Union[int, Int]) -> Int:
    return self + other # self.__add__(other) と同じ

+= も使えるように __iadd__() を実装する.
__riadd__() とかはないっぽい.

ope1 = Int32(1)
ope2 = Int32(2)
ope1 += ope2  # Int(3)

ope1 = Int32(1)
ope2 = 2
ope1 += ope2  # Int(3)

ope1 = 1
ope2 = Int32(2)
ope1 += ope2  # Int(3)
def __iadd__(self, other: Union[int, Int]) -> Int:
    return self + other

ビット演算子用のスペシャルメソッドの実装

算術演算子と同じように __and__()__rand__()__iand__() などを実装する.
ビット演算ではオペランドとして value プロパティではなく bin プロパティを使う.

ope1 = Int(-2, 4)   # 1110
ope2 = Int(6, 4)    # 0110
res = ope1 & ope2   # 0110

ope1 = Int(-2, 4)   # 1110
ope2 = 6            # 0110
res = ope1 & ope2   # 0110

ope1 = -2           # 1110
ope2 = Int(6, 4)    # 0110
res = ope1 & ope2   # 0110


ope1 = Int(-2, 4)   # 1110
ope2 = Int(6, 4)    # 0110
ope1 &= ope2        # 0110

ope1 = Int(-2, 4)   # 1110
ope2 = 6            # 0110
ope1 &= ope2        # 0110

ope1 = -2           # 1110
ope2 = Int(6, 4)    # 0110
ope1 &= ope2        # 0110
def __apply_bit_op(self, ope1: Int, ope2: Int, op: Callable[[int, int], int]) -> Int:
    res = op(ope1.bin, ope2.bin)
    return Int(f"{res:0{ope1.bit}b}", ope1.bit)

...

def __and__(self, other: Union[int, Int]) -> Int:
    ope1, ope2 = self.__validate_opes(other)
    return self.__apply_bit_op(ope1, ope2, operator.and_)

def __rand__(self, other: Union[int, Int]) -> Int:
    ope1, ope2 = self.__validate_opes(other)
    return self.__apply_bit_op(ope2, ope1, operator.and_)

def __iand__(self, other: Union[int, Int]) -> Int:
    return self & other

左シフト用のスペシャルメソッド __lshift__() の実装.
bit 数からはみ出た分をトランケートする処理はバイナリ表記用の整数値 __bin からバイナリ表記を得て,文字列として処理することで行う.
ope1_s[shift:] でシフト分バイナリ文字列をスライスし,('0' * cnt_0) で 0 埋めする.

def __lshift__(self, other: Union[int, Int]) -> Int:
    ope1, ope2 = self.__validate_opes(other)
    ope1_s = f"{ope1.__bin:0{ope1.__bit}b}"
    shift = ope2.value
    cnt_0 = min(ope1.__bit, shift)
    res_s = ope1_s[shift:] + ('0' * cnt_0)
    return Int(res_s, ope1.__bit)

右シフトも同様.
算術右シフトとして実装する (>>> ではなく >>) ので,シフト分のパディングにはバイナリ文字列の MSB を使う (ope1_s[0] * cnt).

def __rshift__(self, other: Union[int, Int]) -> Int:
    ope1, ope2 = self.__validate_opes(other)
    ope1_s = f"{ope1.__bin:0{ope1.__bit}b}"
    shift = ope2.value
    cnt = min(ope1.__bit, shift)
    res_s = (ope1_s[0] * cnt) + ope1_s[:-shift]
    return Int(res_s, ope1.__bit)

~ が適用できるように __invert__() を定義する.
ビット反転させたときのバイナリ表記の 10 進数の値は 補数 - 1 で計算できる.
4 bit int で考える.
x = b0101 をビット反転させると y = b1010 となり,x + y = b1111 だから y = b1111 - b0101
一方,x の補数は b10000 - b00101 = (b01111 + b00001) - b00101
よって,y = x の補数 - 1

n = ~Int(0, 4)      # 0000
print(f"{n:04b}")   # 1111

m = ~Int(5, 4)      # 0101
print(f"{m:04b}")   # 1010
def __invert__(self) -> Int:
    return Int(f"{self.__cmpl(self.__bin) - 1:0{self.__bit}b}", self.__bit)

比較演算子用のスペシャルメソッドの実装

Int オブジェクトどうしはプロパティ value の値の大小で比較する.Built-in の int と遜色なく取り扱えるようにするため,Int オブジェクトどうし以外は value をそのまま比較のオペランドに置く.value の値は int オブジェクトなので int の比較の定義がそのまま使われる.
例として < 用のスペシャルメソッド __lt__() の実装.

print(Int32(-1) < Int32(5)) # True
print(Int32(-1) < 5)  # True
print(-1 < Int32(5))  # True

print(Int32(-1) < 5.0)  # True
print(-1.0 < Int32(5))  # True
def __lt__(self, other) -> bool:
    if isinstance(other, Int):
        return self.value < other.value
    else:
        return self.value < other

コード全体

ソースコードとテスト (https://github.com/e5pe0n/algorithm-training/tree/master/cracking_the_coding_interview_6th/chapter05/python/bitint)

from __future__ import annotations
from typing import Callable, Union, Tuple
import re
import operator


pat = re.compile(r"[01]+")


class Int:
    def __init__(self, value: Union[int, str], bit: int):
        if bit <= 0:
            raise ValueError("bit must be greater than 0.")

        self.__bit: int = bit
        self.__max: int = (1 << (self.__bit - 1)) - 1
        self.__min: int = -self.__max - 1

        if isinstance(value, int):
            # range check
            if value < self.min:
                raise ValueError(
                    f"underflow in {self.__bit}-bit integer value. \
                        value must be between [{self.__min}, {self.__max}]")
            elif self.max < value:
                raise ValueError(
                    f"overflow in {self.bit}-bit integer value. \
                        value must be between [{self.__min}, {self.__max}]")

            if value >= 0:
                self.__value: int = value
                self.__bin: int = self.__value
            else:
                self.__value: int = value
                self.__bin: int = self.__cmpl(abs(self.__value))

        elif isinstance(value, str):
            if len(value) > self.__bit:
                raise ValueError(f"length of value must be between [1, {self.__bit}].")

            if not pat.match(value):
                raise ValueError("value must be a sequence of 0 and 1.")

            if len(value) == self.__bit and value[0] == '1':
                # MSB is 1 so we must calculate the complement
                self.__bin: int = int(value, 2)
                self.__value: int = -self.__cmpl(self.__bin)
            else:
                self.__value: int = int(value, 2)
                self.__bin: int = self.__value

        else:
            raise TypeError("value must be an int or a str object.")

    @property
    def bit(self) -> int:
        return self.__bit

    @property
    def bin(self) -> int:
        return self.__bin

    @property
    def value(self) -> int:
        return self.__value

    @property
    def max(self) -> int:
        return self.__max

    @property
    def min(self) -> int:
        return self.__min

    def __cmpl(self, n: int) -> int:
        return ((self.__max + 1) << 1) - n

    def __validate_opes(self, other: Union[int, Int]) -> Tuple[Int, Int]:
        if isinstance(other, int):
            return (self, Int(other, self.__bit))
        elif isinstance(other, Int):
            greater_bit = max(self.__bit, other.bit)
            return (Int(self.value, greater_bit), Int(other.value, greater_bit))
        else:
            raise TypeError("operands must be int or Int objects")

    def __apply_num_op(self, ope1: Int, ope2: Int, op: Callable[[int, int], int]) -> Int:
        res = op(ope1.value, ope2.value)
        return Int(res, ope1.bit)

    def __apply_bit_op(self, ope1: Int, ope2: Int, op: Callable[[int, int], int]) -> Int:
        res = op(ope1.bin, ope2.bin)
        return Int(f"{res:0{ope1.bit}b}", ope1.bit)

    def __repr__(self) -> str:
        return f"Int(bit={self.__bit}, value={self.__value}, bin={self.__bin})"

    def __str__(self) -> str:
        return str(self.value)

    def __format__(self, format_spec: str) -> str:
        t = format_spec[-1]
        if t in ('b', 'o', 'x', 'X'):
            return format(self.bin, format_spec)
        else:
            return format(self.value, format_spec)

    def __abs__(self) -> Int:
        return abs(self.__value)

    def __neg__(self) -> Int:
        return Int(-self.__value, self.__bit)

    def __add__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope1, ope2, operator.add)

    def __radd__(self, other: Union[int, Int]) -> Int:
        return self + other

    def __iadd__(self, other: Union[int, Int]) -> Int:
        return self + other

    def __sub__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope1, ope2, operator.sub)

    def __rsub__(self, other: Union[int, Int]) -> Int:
        return -self + other

    def __isub__(self, other: Union[int, Int]) -> Int:
        return self - other

    def __mul__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope1, ope2, operator.mul)

    def __rmul__(self, other: Union[int, Int]) -> Int:
        return self * other

    def __imul__(self, other: Union[int, Int]) -> Int:
        return self * other

    def __truediv__(self, other: Union[int, Int]) -> float:
        ope1, ope2 = self.__validate_opes(other)
        return ope1.value / ope2.value

    def __rtruediv__(self, other: Union[int, Int]) -> float:
        ope1, ope2 = self.__validate_opes(other)
        return ope2.value / ope1.value

    def __itruediv__(self, other: Union[int, Int]) -> float:
        return self / other

    def __floordiv__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope1, ope2, operator.floordiv)

    def __rfloordiv__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope2, ope1, operator.floordiv)

    def __ifloordiv__(self, other: Union[int, Int]) -> Int:
        return self // other

    def __mod__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope1, ope2, operator.mod)

    def __rmod__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope2, ope1, operator.mod)

    def __imod__(self, other: Union[int, Int]) -> Int:
        return self % other

    def __pow__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope1, ope2, operator.pow)

    def __rpow__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_num_op(ope2, ope1, operator.pow)

    def __ipow__(self, other: Union[int, Int]) -> Int:
        return self ** other

    def __invert__(self) -> Int:
        return Int(f"{self.__cmpl(self.__bin) - 1:0{self.__bit}b}", self.__bit)

    def __and__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_bit_op(ope1, ope2, operator.and_)

    def __rand__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_bit_op(ope2, ope1, operator.and_)

    def __iand__(self, other: Union[int, Int]) -> Int:
        return self & other

    def __or__(self, other):
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_bit_op(ope1, ope2, operator.or_)

    def __ror__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_bit_op(ope2, ope1, operator.or_)

    def __ior__(self, other: Union[int, Int]) -> Int:
        return self | other

    def __xor__(self, other):
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_bit_op(ope1, ope2, operator.xor)

    def __rxor__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return self.__apply_bit_op(ope2, ope1, operator.xor)

    def __ixor__(self, other: Union[int, Int]) -> Int:
        return self ^ other

    def __lshift__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        ope1_s = f"{ope1.__bin:0{ope1.__bit}b}"
        shift = ope2.value
        cnt_0 = min(ope1.__bit, shift)
        res_s = ope1_s[shift:] + ('0' * cnt_0)
        return Int(res_s, ope1.__bit)

    def __rlshift__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return ope2 << ope1

    def __ilshift__(self, other: Union[int, Int]) -> Int:
        return self << other

    def __rshift__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        ope1_s = f"{ope1.__bin:0{ope1.__bit}b}"
        shift = ope2.value
        cnt = min(ope1.__bit, shift)
        res_s = (ope1_s[0] * cnt) + ope1_s[:-shift]
        return Int(res_s, ope1.__bit)

    def __rrshift__(self, other: Union[int, Int]) -> Int:
        ope1, ope2 = self.__validate_opes(other)
        return ope2 >> ope1

    def __irshift__(self, other: Union[int, Int]) -> Int:
        return self >> other

    def __lt__(self, other) -> bool:
        if isinstance(other, Int):
            return self.value < other.value
        else:
            return self.value < other

    def __le__(self, other) -> bool:
        if isinstance(other, Int):
            return self.value <= other.value
        else:
            return self.value <= other

    def __gt__(self, other) -> bool:
        if isinstance(other, Int):
            return self.value > other.value
        else:
            return self.value > other

    def __ge__(self, other) -> bool:
        if isinstance(other, Int):
            return self.value >= other.value
        else:
            return self.value >= other

    def __eq__(self, other: Union[int, Int]) -> bool:
        if isinstance(other, Int):
            return self.value == other.value
        else:
            return self.value == other

    def __ne__(self, other: Union[int, Int]) -> bool:
        return not (self == other)

    def __bool__(self) -> bool:
        return bool(self.value)

    def __index__(self) -> int:
        return self.bin


def Int16(value):
    return Int(value, 16)


def Int32(value):
    return Int(value, 32)


def Int64(value):
    return Int(value, 64)
3
4
0

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
3
4