string_get_size() rounds the fractional part before formatting the
result. If that carry pushes the integer part to the unit divisor, the
helper still prints the old unit and produces strings like "1000 kB"
and "1024 KiB".

That misformatted carry case comes from the arithmetic rounding added by
commit 564b026fbd0d ("string_helpers: fix precision loss for some
inputs"). The helper already rounds those values up numerically; it
just fails to renormalize them into the next unit before formatting.

Renormalize the carried result into the next named unit so those cases
print as "1.00 MB" and "1.00 MiB" instead. Only do that when another
named unit exists, so the existing "UNK" fallback is preserved at the
top end. Extend the KUnit coverage around the first decimal and binary
boundaries, including adjacent values and non-byte block sizes that hit
the same carry path.

Fixes: 564b026fbd0d ("string_helpers: fix precision loss for some inputs")
Signed-off-by: Shuvam Pandey <[email protected]>
---
Changes in v2:
- correct the Fixes tag to 564b026fbd0d
- renormalize only when another named unit exists
- preserve the top-end UNK fallback
- add decimal and binary boundary/neighbor KUnit coverage
- add non-byte block-size cases that hit the same carry path

 lib/string_helpers.c             | 12 ++++++++++++
 lib/tests/string_helpers_kunit.c | 25 +++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/lib/string_helpers.c b/lib/string_helpers.c
index 169eaf5834949..bcabb24fe0dbf 100644
--- a/lib/string_helpers.c
+++ b/lib/string_helpers.c
@@ -121,6 +121,18 @@ int string_get_size(u64 size, u64 blk_size, const enum 
string_size_units units,
                size += 1;
        }
 
+       /*
+        * Renormalize into the next named unit, but preserve the top-end
+        * UNK fallback. After promotion the value is exactly 1 of the next
+        * unit, so keep two fractional digits for the usual 1.00 formatting.
+        */
+       if (size >= divisor[units_base] && i + 1 < ARRAY_SIZE(units_2)) {
+               size = 1;
+               remainder = 0;
+               i++;
+               j = 2;
+       }
+
        if (j) {
                snprintf(tmp, sizeof(tmp), ".%03u", remainder);
                tmp[j+1] = '\0';
diff --git a/lib/tests/string_helpers_kunit.c b/lib/tests/string_helpers_kunit.c
index c853046183d24..67822cb48fd15 100644
--- a/lib/tests/string_helpers_kunit.c
+++ b/lib/tests/string_helpers_kunit.c
@@ -558,6 +558,31 @@ static void test_get_size(struct kunit *test)
        /* weird block sizes */
        test_string_get_size_one(3000, 1900, "5.70 MB", "5.44 MiB");
 
+       /* rounding carry into the next unit at the first decimal boundary */
+       test_string_get_size_one(999499, 1, "999 kB", "976 KiB");
+       test_string_get_size_one(999500, 1, "1.00 MB", "976 KiB");
+       test_string_get_size_one(999999, 1, "1.00 MB", "977 KiB");
+       test_string_get_size_one(1000000, 1, "1.00 MB", "977 KiB");
+       test_string_get_size_one(1000001, 1, "1.00 MB", "977 KiB");
+
+       /* rounding carry into the next unit at the first binary boundary */
+       test_string_get_size_one(1048063, 1, "1.05 MB", "1023 KiB");
+       test_string_get_size_one(1048064, 1, "1.05 MB", "1.00 MiB");
+       test_string_get_size_one(1048575, 1, "1.05 MB", "1.00 MiB");
+       test_string_get_size_one(1048576, 1, "1.05 MB", "1.00 MiB");
+       test_string_get_size_one(1048577, 1, "1.05 MB", "1.00 MiB");
+
+       /* values already in the next binary unit stay unchanged */
+       test_string_get_size_one(2097151, 1, "2.10 MB", "2.00 MiB");
+       test_string_get_size_one(2097152, 1, "2.10 MB", "2.00 MiB");
+       test_string_get_size_one(2097153, 1, "2.10 MB", "2.00 MiB");
+
+       /* non-byte block sizes hit the same carry path */
+       test_string_get_size_one(4997, 200, "999 kB", "976 KiB");
+       test_string_get_size_one(4998, 200, "1.00 MB", "976 KiB");
+       test_string_get_size_one(9981, 105, "1.05 MB", "1023 KiB");
+       test_string_get_size_one(9982, 105, "1.05 MB", "1.00 MiB");
+
        /* huge values */
        test_string_get_size_one(U64_MAX, 4096, "75.6 ZB", "64.0 ZiB");
        test_string_get_size_one(4096, U64_MAX, "75.6 ZB", "64.0 ZiB");
-- 
2.50.1 (Apple Git-155)


Reply via email to