From 60c4d657dfa4a60b3e30e441733dc43f232c7d1d Mon Sep 17 00:00:00 2001 From: Shahadat Islam Koyel <179203101+ShahadatTest@users.noreply.github.com> Date: Sat, 31 Oct 2026 23:57:38 +0600 Subject: [PATCH] Keep binary prefixes below their exact size thresholds --- _python_utils_tests/test_converters.py | 36 ++++++++++++++++++++++++++ python_utils/converters.py | 8 ++++++ 2 files changed, 44 insertions(+) diff --git a/_python_utils_tests/test_converters.py b/_python_utils_tests/test_converters.py index 2d6c8e0..e9e312f 100644 --- a/_python_utils_tests/test_converters.py +++ b/_python_utils_tests/test_converters.py @@ -1,5 +1,6 @@ """Tests for the conversion helpers in ``python_utils.converters``.""" +import math import re import pytest @@ -33,6 +34,41 @@ def test_scale_1024_small_number(value: float) -> None: assert converters.scale_1024(value, 9) == (value, 0) +@pytest.mark.parametrize('power', range(1, 9)) +def test_scale_1024_integer_prefix_boundaries(power: int) -> None: + """Choose prefixes from the integer value before rounding it to float.""" + boundary = 1024**power + for value, expected_power in ( + (boundary - 1, power - 1), + (boundary, power), + (boundary + 1, power), + ): + assert converters.scale_1024(value, 9) == ( + float(value) / 1024**expected_power, + expected_power, + ) + + +@pytest.mark.parametrize('power', range(1, 9)) +def test_scale_1024_float_prefix_boundaries(power: int) -> None: + """Keep the float immediately below a prefix boundary in the lower unit.""" + boundary = float(1024**power) + for value, expected_power in ( + (math.nextafter(boundary, 0.0), power - 1), + (boundary, power), + (math.nextafter(boundary, math.inf), power), + ): + assert converters.scale_1024(value, 9) == ( + value / 1024**expected_power, + expected_power, + ) + + +def test_scale_1024_prefix_limit() -> None: + """Keep values in the largest available prefix even at higher powers.""" + assert converters.scale_1024(1024**5, 3) == (1024.0**3, 2) + + @pytest.mark.parametrize('n_prefixes', [0, -1]) def test_scale_1024_without_prefixes(n_prefixes: int) -> None: """Never return a negative power when there are no prefixes.""" diff --git a/python_utils/converters.py b/python_utils/converters.py index 35340de..3921798 100644 --- a/python_utils/converters.py +++ b/python_utils/converters.py @@ -308,6 +308,9 @@ def scale_1024( Use to format numbers of bytes to KiB, MiB, etc. + A prefix is used only when the input reaches its power of 1024. For + integer inputs, the prefix is chosen before converting the value to float. + >>> scale_1024(310, 3) (310.0, 0) >>> scale_1024(2048, 3) @@ -325,6 +328,11 @@ def scale_1024( # Never below zero: a number under 1 has a negative logarithm, and a # negative power would index the prefixes from the wrong end. power = max(min(int(math.log(x, 2) / 10), n_prefixes - 1), 0) + # A logarithm can round a value just below a prefix boundary up to + # that boundary. Compare with the exact integer power before the + # input itself is rounded to float. + if power and x < 2 ** (10 * power): + power -= 1 scaled = float(x) / (2 ** (10 * power)) return scaled, power