import hashlib
MASK64 = (1 << 64) - 1
EXP_MASK = 0x7FF
FRAC_MASK = (1 << 52) - 1
def _total_key(bits):
if (bits >> 63) & 1:
return (~bits) & MASK64
return bits | (1 << 63)
def _uvarint(x):
out = bytearray()
while True:
b = x & 0x7F
x >>= 7
if x == 0:
out.append(b)
return bytes(out)
out.append(b | 0x80)
class RelSketch:
def __init__(self, sub_bits):
assert 1 <= sub_bits <= 52
self.sub_bits = sub_bits
self.collapse_shift = 0
self.shift = 52 - sub_bits self.pos = {}
self.neg = {}
self.zero = 0
self.nan = 0
self.pos_inf = 0
self.neg_inf = 0
self.count = 0
self.min_bits = 0x7FF0000000000000 self.max_bits = 0xFFF0000000000000 self.mismatched = False
def add_bits(self, bits):
bits &= MASK64
self.count += 1
exp = (bits >> 52) & EXP_MASK
frac = bits & FRAC_MASK
sign = (bits >> 63) & 1
if exp == EXP_MASK and frac != 0: self.nan += 1
return
if _total_key(bits) < _total_key(self.min_bits):
self.min_bits = bits
if _total_key(bits) > _total_key(self.max_bits):
self.max_bits = bits
if exp == EXP_MASK: if sign:
self.neg_inf += 1
else:
self.pos_inf += 1
elif exp == 0 and frac == 0: self.zero += 1
elif sign == 0: key = bits >> self.shift
self.pos[key] = self.pos.get(key, 0) + 1
else: mag = bits & 0x7FFFFFFFFFFFFFFF
key = mag >> self.shift
self.neg[key] = self.neg.get(key, 0) + 1
def _write_map(self, m):
out = bytearray(_uvarint(len(m)))
prev = None
for k in sorted(m):
delta = k if prev is None else k - prev
out += _uvarint(delta)
out += _uvarint(m[k])
prev = k
return bytes(out)
def to_bytes(self):
out = bytearray([self.sub_bits, self.collapse_shift, int(self.mismatched)])
for v in (self.nan, self.pos_inf, self.neg_inf, self.zero,
self.min_bits, self.max_bits, self.count):
out += int(v).to_bytes(8, "little")
out += self._write_map(self.pos)
out += self._write_map(self.neg)
return bytes(out)
def state_hash(self):
return hashlib.sha256(self.to_bytes()).digest()
def merge(self, other):
assert self.sub_bits == other.sub_bits
for k, c in other.pos.items():
self.pos[k] = self.pos.get(k, 0) + c
for k, c in other.neg.items():
self.neg[k] = self.neg.get(k, 0) + c
self.zero += other.zero
self.nan += other.nan
self.pos_inf += other.pos_inf
self.neg_inf += other.neg_inf
if _total_key(other.min_bits) < _total_key(self.min_bits):
self.min_bits = other.min_bits
if _total_key(other.max_bits) > _total_key(self.max_bits):
self.max_bits = other.max_bits
self.count += other.count