From b1a3b28a20e91588ba1d08d98be75c99be0fb628 Mon Sep 17 00:00:00 2001 From: Arthur Meyre Date: Fri, 30 Jul 2021 14:14:34 +0200 Subject: [PATCH] dev(dtypes): add a function to make Integers able to hold a set of values --- hdk/common/data_types/integers.py | 60 ++++++++++++++++++++++++ tests/common/data_types/test_integers.py | 44 ++++++++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/hdk/common/data_types/integers.py b/hdk/common/data_types/integers.py index 91b34d992..49c04e23f 100644 --- a/hdk/common/data_types/integers.py +++ b/hdk/common/data_types/integers.py @@ -1,5 +1,8 @@ """This file holds the definitions for integer types""" +import math +from typing import Iterable + from . import base @@ -79,3 +82,60 @@ def create_unsigned_integer(bit_width: int) -> Integer: UnsignedInteger = create_unsigned_integer + + +def make_integer_to_hold_ints(values: Iterable[int], force_signed: bool) -> Integer: + """Returns an Integer able to hold all values, it is possible to force the Integer to be signed + + Args: + values (Iterable[int]): The values to hold + force_signed (bool): Set to True to force the result to be a signed Integer + + Returns: + Integer: The Integer able to hold values + """ + assert all(map(lambda x: isinstance(x, int), values)) + min_value = min(values) + max_value = max(values) + + make_signed_integer = force_signed or min_value < 0 + + num_bits = max( + get_bits_to_represent_int(min_value, make_signed_integer), + get_bits_to_represent_int(max_value, make_signed_integer), + ) + + return Integer(num_bits, is_signed=make_signed_integer) + + +def get_bits_to_represent_int(value: int, force_signed: bool) -> int: + """Returns how many bits are required to represent a single int + + Args: + value (int): The int for which we want to know how many bits are required + force_signed (bool): Set to True to force the result to be a signed Integer + + Returns: + int: required amount of bits + """ + + # Writing this in a very dumb way + num_bits: int + if value < 0: + abs_value = abs(value) + if abs_value > 1: + num_bits = math.ceil(math.log2(abs_value)) + 1 + else: + # -1 case + num_bits = 2 + else: + if value > 1: + num_bits = math.ceil(math.log2(value + 1)) + else: + # 0 and 1 case + num_bits = 1 + + if force_signed: + num_bits += 1 + + return num_bits diff --git a/tests/common/data_types/test_integers.py b/tests/common/data_types/test_integers.py index 332db50df..c4ed8f1fc 100644 --- a/tests/common/data_types/test_integers.py +++ b/tests/common/data_types/test_integers.py @@ -4,7 +4,12 @@ import random import pytest -from hdk.common.data_types.integers import Integer, SignedInteger, UnsignedInteger +from hdk.common.data_types.integers import ( + Integer, + SignedInteger, + UnsignedInteger, + make_integer_to_hold_ints, +) @pytest.mark.parametrize( @@ -68,3 +73,40 @@ def test_basic_integers(integer: Integer, expected_min: int, expected_max: int): def test_integers_repr(integer: Integer, expected_repr_str: str): """Test integer repr""" assert integer.__repr__() == expected_repr_str + + +@pytest.mark.parametrize( + "values,force_signed,expected_result", + [ + ([0], False, Integer(1, is_signed=False)), + ([0], True, Integer(2, is_signed=True)), + ([1], False, Integer(1, is_signed=False)), + ([1], True, Integer(2, is_signed=True)), + ([-1], False, Integer(2, is_signed=True)), + ([-2], False, Integer(2, is_signed=True)), + ([0, 1], False, Integer(1, is_signed=False)), + ([0, 1], True, Integer(2, is_signed=True)), + ([7], False, Integer(3, is_signed=False)), + ([7], True, Integer(4, is_signed=True)), + ([8], False, Integer(4, is_signed=False)), + ([8], True, Integer(5, is_signed=True)), + ([-7], False, Integer(4, is_signed=True)), + ([-8], False, Integer(4, is_signed=True)), + ([-7, -8], False, Integer(4, is_signed=True)), + ([-9], False, Integer(5, is_signed=True)), + ([-9], True, Integer(5, is_signed=True)), + ([0, 127], False, Integer(7, is_signed=False)), + ([0, 127], True, Integer(8, is_signed=True)), + ([0, 128], False, Integer(8, is_signed=False)), + ([0, 128], True, Integer(9, is_signed=True)), + ([-1, 127], False, Integer(8, is_signed=True)), + ([-256, 127], False, Integer(9, is_signed=True)), + ([-128, 127], False, Integer(8, is_signed=True)), + ([-128, 128], False, Integer(9, is_signed=True)), + ([-13, 4], False, Integer(5, is_signed=True)), + ([42, 1019], False, Integer(10, is_signed=False)), + ], +) +def test_make_integer_to_hold(values, force_signed, expected_result): + """Test make_integer_to_hold""" + assert expected_result == make_integer_to_hold_ints(values, force_signed)