From eae1bc0ce64b7d62372fa34b7952c19dca757d7d Mon Sep 17 00:00:00 2001 From: Pierre Gimalac Date: Mon, 21 Apr 2025 00:59:18 +0200 Subject: [PATCH 1/4] feat: support negative values --- binary/core.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/binary/core.py b/binary/core.py index c414094..2f2d2e1 100644 --- a/binary/core.py +++ b/binary/core.py @@ -170,40 +170,40 @@ def convert_units( raise ValueError(f'{to} is not a valid unit.') if unit in BINARY_PREFIXES and not si: - if b < KIBIBYTE: + if abs(b) < KIBIBYTE: return b, 'B' - elif b < MEBIBYTE: + elif abs(b) < MEBIBYTE: return b / KIBIBYTE, 'KiB' - elif b < GIBIBYTE: + elif abs(b) < GIBIBYTE: return b / MEBIBYTE, 'MiB' - elif b < TEBIBYTE: + elif abs(b) < TEBIBYTE: return b / GIBIBYTE, 'GiB' - elif b < PEBIBYTE: + elif abs(b) < PEBIBYTE: return b / TEBIBYTE, 'TiB' - elif b < EXBIBYTE: + elif abs(b) < EXBIBYTE: return b / PEBIBYTE, 'PiB' - elif b < ZEBIBYTE: + elif abs(b) < ZEBIBYTE: return b / EXBIBYTE, 'EiB' - elif b < YOBIBYTE: + elif abs(b) < YOBIBYTE: return b / ZEBIBYTE, 'ZiB' else: return b / YOBIBYTE, 'YiB' else: - if b < KILOBYTE: + if abs(b) < KILOBYTE: return b, 'B' - elif b < MEGABYTE: + elif abs(b) < MEGABYTE: return b / KILOBYTE, 'KB' - elif b < GIGABYTE: + elif abs(b) < GIGABYTE: return b / MEGABYTE, 'MB' - elif b < TERABYTE: + elif abs(b) < TERABYTE: return b / GIGABYTE, 'GB' - elif b < PETABYTE: + elif abs(b) < PETABYTE: return b / TERABYTE, 'TB' - elif b < EXABYTE: + elif abs(b) < EXABYTE: return b / PETABYTE, 'PB' - elif b < ZETTABYTE: + elif abs(b) < ZETTABYTE: return b / EXABYTE, 'EB' - elif b < YOTTABYTE: + elif abs(b) < YOTTABYTE: return b / ZETTABYTE, 'ZB' else: return b / YOTTABYTE, 'YB' From 230494bb017fa3444199511133966731710d595d Mon Sep 17 00:00:00 2001 From: Pierre Gimalac Date: Mon, 21 Apr 2025 01:23:38 +0200 Subject: [PATCH 2/4] test: negative values --- tests/test_core.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index c12a059..c440bed 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -121,108 +121,144 @@ class TestConvert: def test_byte(self) -> None: assert convert_units(1, bunits.YB, bunits.B) == (bunits.YB // 1, 'B') assert convert_units(1, dunits.YB, dunits.B) == (dunits.YB // 1, 'B') + assert convert_units(-1, bunits.YB, bunits.B) == (-bunits.YB // 1, 'B') + assert convert_units(-1, dunits.YB, dunits.B) == (-dunits.YB // 1, 'B') def test_kibibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.KB) == (bunits.YB / 1024 ** 1, 'KiB') + assert convert_units(-1, bunits.YB, bunits.KB) == (-bunits.YB / 1024 ** 1, 'KiB') def test_mebibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.MB) == (bunits.YB / 1024 ** 2, 'MiB') + assert convert_units(-1, bunits.YB, bunits.MB) == (-bunits.YB / 1024 ** 2, 'MiB') def test_gibibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.GB) == (bunits.YB / 1024 ** 3, 'GiB') + assert convert_units(-1, bunits.YB, bunits.GB) == (-bunits.YB / 1024 ** 3, 'GiB') def test_tebibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.TB) == (bunits.YB / 1024 ** 4, 'TiB') + assert convert_units(-1, bunits.YB, bunits.TB) == (-bunits.YB / 1024 ** 4, 'TiB') def test_pebibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.PB) == (bunits.YB / 1024 ** 5, 'PiB') + assert convert_units(-1, bunits.YB, bunits.PB) == (-bunits.YB / 1024 ** 5, 'PiB') def test_exbibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.EB) == (bunits.YB / 1024 ** 6, 'EiB') + assert convert_units(-1, bunits.YB, bunits.EB) == (-bunits.YB / 1024 ** 6, 'EiB') def test_zebibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.ZB) == (bunits.YB / 1024 ** 7, 'ZiB') + assert convert_units(-1, bunits.YB, bunits.ZB) == (-bunits.YB / 1024 ** 7, 'ZiB') def test_yobibyte(self) -> None: assert convert_units(1, bunits.YB, bunits.YB) == (bunits.YB / 1024 ** 8, 'YiB') + assert convert_units(-1, bunits.YB, bunits.YB) == (-bunits.YB / 1024 ** 8, 'YiB') def test_kilobyte(self) -> None: assert convert_units(1, dunits.YB, dunits.KB) == (dunits.YB / 1000 ** 1, 'KB') + assert convert_units(-1, dunits.YB, dunits.KB) == (-dunits.YB / 1000 ** 1, 'KB') def test_megabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.MB) == (dunits.YB / 1000 ** 2, 'MB') + assert convert_units(-1, dunits.YB, dunits.MB) == (-dunits.YB / 1000 ** 2, 'MB') def test_gigabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.GB) == (dunits.YB / 1000 ** 3, 'GB') + assert convert_units(-1, dunits.YB, dunits.GB) == (-dunits.YB / 1000 ** 3, 'GB') def test_terabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.TB) == (dunits.YB / 1000 ** 4, 'TB') + assert convert_units(-1, dunits.YB, dunits.TB) == (-dunits.YB / 1000 ** 4, 'TB') def test_petabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.PB) == (dunits.YB / 1000 ** 5, 'PB') + assert convert_units(-1, dunits.YB, dunits.PB) == (-dunits.YB / 1000 ** 5, 'PB') def test_exabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.EB) == (dunits.YB / 1000 ** 6, 'EB') + assert convert_units(-1, dunits.YB, dunits.EB) == (-dunits.YB / 1000 ** 6, 'EB') def test_zettabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.ZB) == (dunits.YB / 1000 ** 7, 'ZB') + assert convert_units(-1, dunits.YB, dunits.ZB) == (-dunits.YB / 1000 ** 7, 'ZB') def test_yottabyte(self) -> None: assert convert_units(1, dunits.YB, dunits.YB) == (dunits.YB / 1000 ** 8, 'YB') + assert convert_units(-1, dunits.YB, dunits.YB) == (-dunits.YB / 1000 ** 8, 'YB') class TestConvertFloatExact: def test_byte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.B, exact=True) == (Decimal('3796027073589935608577392'), 'B') assert convert_units(3.14, dunits.YB, dunits.B, exact=True) == (Decimal('3140000000000000000000000'), 'B') + assert convert_units(-3.14, bunits.YB, bunits.B, exact=True) == (Decimal('-3796027073589935608577392'), 'B') + assert convert_units(-3.14, dunits.YB, dunits.B, exact=True) == (Decimal('-3140000000000000000000000'), 'B') def test_kibibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.KB, exact=True) == (Decimal('3707057689052671492751.36'), 'KiB') + assert convert_units(-3.14, bunits.YB, bunits.KB, exact=True) == (Decimal('-3707057689052671492751.36'), 'KiB') def test_mebibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.MB, exact=True) == (Decimal('3620173524465499504.64'), 'MiB') + assert convert_units(-3.14, bunits.YB, bunits.MB, exact=True) == (Decimal('-3620173524465499504.64'), 'MiB') def test_gibibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.GB, exact=True) == (Decimal('3535325707485839.36'), 'GiB') + assert convert_units(-3.14, bunits.YB, bunits.GB, exact=True) == (Decimal('-3535325707485839.36'), 'GiB') def test_tebibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.TB, exact=True) == (Decimal('3452466511216.64'), 'TiB') + assert convert_units(-3.14, bunits.YB, bunits.TB, exact=True) == (Decimal('-3452466511216.64'), 'TiB') def test_pebibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.PB, exact=True) == (Decimal('3371549327.36'), 'PiB') + assert convert_units(-3.14, bunits.YB, bunits.PB, exact=True) == (Decimal('-3371549327.36'), 'PiB') def test_exbibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.EB, exact=True) == (Decimal('3292528.64'), 'EiB') + assert convert_units(-3.14, bunits.YB, bunits.EB, exact=True) == (Decimal('-3292528.64'), 'EiB') def test_zebibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.ZB, exact=True) == (Decimal('3215.36'), 'ZiB') + assert convert_units(-3.14, bunits.YB, bunits.ZB, exact=True) == (Decimal('-3215.36'), 'ZiB') def test_yobibyte(self) -> None: assert convert_units(3.14, bunits.YB, bunits.YB, exact=True) == (Decimal('3.14'), 'YiB') + assert convert_units(-3.14, bunits.YB, bunits.YB, exact=True) == (Decimal('-3.14'), 'YiB') def test_kilobyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.KB, exact=True) == (Decimal('3140000000000000000000.00'), 'KB') + assert convert_units(-3.14, dunits.YB, dunits.KB, exact=True) == (Decimal('-3140000000000000000000.00'), 'KB') def test_megabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.MB, exact=True) == (Decimal('3140000000000000000.00'), 'MB') + assert convert_units(-3.14, dunits.YB, dunits.MB, exact=True) == (Decimal('-3140000000000000000.00'), 'MB') def test_gigabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.GB, exact=True) == (Decimal('3140000000000000.00'), 'GB') + assert convert_units(-3.14, dunits.YB, dunits.GB, exact=True) == (Decimal('-3140000000000000.00'), 'GB') def test_terabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.TB, exact=True) == (Decimal('3140000000000.00'), 'TB') + assert convert_units(-3.14, dunits.YB, dunits.TB, exact=True) == (Decimal('-3140000000000.00'), 'TB') def test_petabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.PB, exact=True) == (Decimal('3140000000.00'), 'PB') + assert convert_units(-3.14, dunits.YB, dunits.PB, exact=True) == (Decimal('-3140000000.00'), 'PB') def test_exabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.EB, exact=True) == (Decimal('3140000.00'), 'EB') + assert convert_units(-3.14, dunits.YB, dunits.EB, exact=True) == (Decimal('-3140000.00'), 'EB') def test_zettabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.ZB, exact=True) == (Decimal('3140.00'), 'ZB') + assert convert_units(-3.14, dunits.YB, dunits.ZB, exact=True) == (Decimal('-3140.00'), 'ZB') def test_yottabyte(self) -> None: assert convert_units(3.14, dunits.YB, dunits.YB, exact=True) == (Decimal('3.14'), 'YB') + assert convert_units(-3.14, dunits.YB, dunits.YB, exact=True) == (Decimal('-3.14'), 'YB') class TestConvertUnknownTo: @@ -231,68 +267,102 @@ def test_byte(self) -> None: assert convert_units(bunits.KB - 1) == (bunits.KB - 1, 'B') assert convert_units(dunits.B, si=True) == (dunits.B, 'B') assert convert_units(dunits.KB - 1, si=True) == (dunits.KB - 1, 'B') + assert convert_units(-bunits.B) == (-bunits.B, 'B') + assert convert_units(-bunits.KB + 1) == (-bunits.KB + 1, 'B') + assert convert_units(-dunits.B, si=True) == (-dunits.B, 'B') + assert convert_units(-dunits.KB + 1, si=True) == (-dunits.KB + 1, 'B') def test_kibibyte(self) -> None: assert convert_units(bunits.KB) == (bunits.KB / bunits.KB, 'KiB') assert convert_units(bunits.MB - 1) == ((bunits.MB - 1) / bunits.KB, 'KiB') + assert convert_units(-bunits.KB) == (-bunits.KB / bunits.KB, 'KiB') + assert convert_units(-bunits.MB + 1) == ((-bunits.MB + 1) / bunits.KB, 'KiB') def test_mebibyte(self) -> None: assert convert_units(bunits.MB) == (bunits.MB / bunits.MB, 'MiB') assert convert_units(bunits.GB - 1) == ((bunits.GB - 1) / bunits.MB, 'MiB') + assert convert_units(-bunits.MB) == (-bunits.MB / bunits.MB, 'MiB') + assert convert_units(-bunits.GB + 1) == ((-bunits.GB + 1) / bunits.MB, 'MiB') def test_gibibyte(self) -> None: assert convert_units(bunits.GB) == (bunits.GB / bunits.GB, 'GiB') assert convert_units(bunits.TB - 1) == ((bunits.TB - 1) / bunits.GB, 'GiB') + assert convert_units(-bunits.GB) == (-bunits.GB / bunits.GB, 'GiB') + assert convert_units(-bunits.TB + 1) == ((-bunits.TB + 1) / bunits.GB, 'GiB') def test_tebibyte(self) -> None: assert convert_units(bunits.TB) == (bunits.TB / bunits.TB, 'TiB') assert convert_units(bunits.PB - 1) == ((bunits.PB - 1) / bunits.TB, 'TiB') + assert convert_units(-bunits.TB) == (-bunits.TB / bunits.TB, 'TiB') + assert convert_units(-bunits.PB + 1) == ((-bunits.PB + 1) / bunits.TB, 'TiB') def test_pebibyte(self) -> None: assert convert_units(bunits.PB) == (bunits.PB / bunits.PB, 'PiB') assert convert_units(bunits.EB - 1) == ((bunits.EB - 1) / bunits.PB, 'PiB') + assert convert_units(-bunits.PB) == (-bunits.PB / bunits.PB, 'PiB') + assert convert_units(-bunits.EB + 1) == ((-bunits.EB + 1) / bunits.PB, 'PiB') def test_exbibyte(self) -> None: assert convert_units(bunits.EB) == (bunits.EB / bunits.EB, 'EiB') assert convert_units(bunits.ZB - 1) == ((bunits.ZB - 1) / bunits.EB, 'EiB') + assert convert_units(-bunits.EB) == (-bunits.EB / bunits.EB, 'EiB') + assert convert_units(-bunits.ZB + 1) == ((-bunits.ZB + 1) / bunits.EB, 'EiB') def test_zebibyte(self) -> None: assert convert_units(bunits.ZB) == (bunits.ZB / bunits.ZB, 'ZiB') assert convert_units(bunits.YB - 1) == ((bunits.YB - 1) / bunits.ZB, 'ZiB') + assert convert_units(-bunits.ZB) == (-bunits.ZB / bunits.ZB, 'ZiB') + assert convert_units(-bunits.YB + 1) == ((-bunits.YB + 1) / bunits.ZB, 'ZiB') def test_yobibyte(self) -> None: assert convert_units(bunits.YB) == (bunits.YB / bunits.YB, 'YiB') + assert convert_units(-bunits.YB) == (-bunits.YB / bunits.YB, 'YiB') def test_kilobyte(self) -> None: assert convert_units(dunits.KB, si=True) == (dunits.KB / dunits.KB, 'KB') assert convert_units(dunits.MB - 1, si=True) == ((dunits.MB - 1) / dunits.KB, 'KB') + assert convert_units(-dunits.KB, si=True) == (-dunits.KB / dunits.KB, 'KB') + assert convert_units(-dunits.MB + 1, si=True) == ((-dunits.MB + 1) / dunits.KB, 'KB') def test_megabyte(self) -> None: assert convert_units(dunits.MB, si=True) == (dunits.MB / dunits.MB, 'MB') assert convert_units(dunits.GB - 1, si=True) == ((dunits.GB - 1) / dunits.MB, 'MB') + assert convert_units(-dunits.MB, si=True) == (-dunits.MB / dunits.MB, 'MB') + assert convert_units(-dunits.GB + 1, si=True) == ((-dunits.GB + 1) / dunits.MB, 'MB') def test_gigabyte(self) -> None: assert convert_units(dunits.GB, si=True) == (dunits.GB / dunits.GB, 'GB') assert convert_units(dunits.TB - 1, si=True) == ((dunits.TB - 1) / dunits.GB, 'GB') + assert convert_units(-dunits.GB, si=True) == (-dunits.GB / dunits.GB, 'GB') + assert convert_units(-dunits.TB + 1, si=True) == ((-dunits.TB + 1) / dunits.GB, 'GB') def test_terabyte(self) -> None: assert convert_units(dunits.TB, si=True) == (dunits.TB / dunits.TB, 'TB') assert convert_units(dunits.PB - 1, si=True) == ((dunits.PB - 1) / dunits.TB, 'TB') + assert convert_units(-dunits.TB, si=True) == (-dunits.TB / dunits.TB, 'TB') + assert convert_units(-dunits.PB + 1, si=True) == ((-dunits.PB + 1) / dunits.TB, 'TB') def test_petabyte(self) -> None: assert convert_units(dunits.PB, si=True) == (dunits.PB / dunits.PB, 'PB') assert convert_units(dunits.EB - 1, si=True) == ((dunits.EB - 1) / dunits.PB, 'PB') + assert convert_units(-dunits.PB, si=True) == (-dunits.PB / dunits.PB, 'PB') + assert convert_units(-dunits.EB + 1, si=True) == ((-dunits.EB + 1) / dunits.PB, 'PB') def test_exabyte(self) -> None: assert convert_units(dunits.EB, si=True) == (dunits.EB / dunits.EB, 'EB') assert convert_units(dunits.ZB - 1, si=True) == ((dunits.ZB - 1) / dunits.EB, 'EB') + assert convert_units(-dunits.EB, si=True) == (-dunits.EB / dunits.EB, 'EB') + assert convert_units(-dunits.ZB + 1, si=True) == ((-dunits.ZB + 1) / dunits.EB, 'EB') def test_zettabyte(self) -> None: assert convert_units(dunits.ZB, si=True) == (dunits.ZB / dunits.ZB, 'ZB') assert convert_units(dunits.YB - 1, si=True) == ((dunits.YB - 1) / dunits.ZB, 'ZB') + assert convert_units(-dunits.ZB, si=True) == (-dunits.ZB / dunits.ZB, 'ZB') + assert convert_units(-dunits.YB + 1, si=True) == ((-dunits.YB + 1) / dunits.ZB, 'ZB') def test_yottabyte(self) -> None: assert convert_units(dunits.YB, si=True) == (dunits.YB / dunits.YB, 'YB') + assert convert_units(-dunits.YB, si=True) == (-dunits.YB / dunits.YB, 'YB') class TestUnknownUnits: From 1b42b8877b92427264be50aee38740bb99bb5b74 Mon Sep 17 00:00:00 2001 From: Pierre Gimalac Date: Mon, 21 Apr 2025 11:35:52 +0200 Subject: [PATCH 3/4] chore: cache abs value, and fix linter --- binary/core.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/binary/core.py b/binary/core.py index 2f2d2e1..f17cdb5 100644 --- a/binary/core.py +++ b/binary/core.py @@ -1,5 +1,5 @@ from decimal import Decimal -from typing import NamedTuple, Optional, Tuple, Union +from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple, Union BYTE = 1 @@ -169,41 +169,45 @@ def convert_units( except KeyError: raise ValueError(f'{to} is not a valid unit.') + babs = abs(b) + if TYPE_CHECKING: + assert isinstance(babs, float) or isinstance(babs, Decimal) + if unit in BINARY_PREFIXES and not si: - if abs(b) < KIBIBYTE: + if babs < KIBIBYTE: return b, 'B' - elif abs(b) < MEBIBYTE: + elif babs < MEBIBYTE: return b / KIBIBYTE, 'KiB' - elif abs(b) < GIBIBYTE: + elif babs < GIBIBYTE: return b / MEBIBYTE, 'MiB' - elif abs(b) < TEBIBYTE: + elif babs < TEBIBYTE: return b / GIBIBYTE, 'GiB' - elif abs(b) < PEBIBYTE: + elif babs < PEBIBYTE: return b / TEBIBYTE, 'TiB' - elif abs(b) < EXBIBYTE: + elif babs < EXBIBYTE: return b / PEBIBYTE, 'PiB' - elif abs(b) < ZEBIBYTE: + elif babs < ZEBIBYTE: return b / EXBIBYTE, 'EiB' - elif abs(b) < YOBIBYTE: + elif babs < YOBIBYTE: return b / ZEBIBYTE, 'ZiB' else: return b / YOBIBYTE, 'YiB' else: - if abs(b) < KILOBYTE: + if babs < KILOBYTE: return b, 'B' - elif abs(b) < MEGABYTE: + elif babs < MEGABYTE: return b / KILOBYTE, 'KB' - elif abs(b) < GIGABYTE: + elif babs < GIGABYTE: return b / MEGABYTE, 'MB' - elif abs(b) < TERABYTE: + elif babs < TERABYTE: return b / GIGABYTE, 'GB' - elif abs(b) < PETABYTE: + elif babs < PETABYTE: return b / TERABYTE, 'TB' - elif abs(b) < EXABYTE: + elif babs < EXABYTE: return b / PETABYTE, 'PB' - elif abs(b) < ZETTABYTE: + elif babs < ZETTABYTE: return b / EXABYTE, 'EB' - elif abs(b) < YOTTABYTE: + elif babs < YOTTABYTE: return b / ZETTABYTE, 'ZB' else: return b / YOTTABYTE, 'YB' From 375d8c204f4ff48f667cbfd04ad49e649f9129e5 Mon Sep 17 00:00:00 2001 From: Pierre Gimalac Date: Tue, 22 Apr 2025 10:39:08 +0200 Subject: [PATCH 4/4] chore: use typing.cast instead of type assertion --- binary/core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/binary/core.py b/binary/core.py index f17cdb5..c9a0f88 100644 --- a/binary/core.py +++ b/binary/core.py @@ -1,5 +1,6 @@ from decimal import Decimal from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple, Union +import typing BYTE = 1 @@ -169,9 +170,7 @@ def convert_units( except KeyError: raise ValueError(f'{to} is not a valid unit.') - babs = abs(b) - if TYPE_CHECKING: - assert isinstance(babs, float) or isinstance(babs, Decimal) + babs = typing.cast(Union[float, Decimal], abs(b)) if unit in BINARY_PREFIXES and not si: if babs < KIBIBYTE: