]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/strutils: parse_size() fix frac digit calculation
authorKarel Zak <kzak@redhat.com>
Mon, 13 May 2019 15:07:14 +0000 (17:07 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 13 May 2019 15:07:14 +0000 (17:07 +0200)
Old code:

./test_strutils --size 0.5MiB
0.5MiB :               512000 :     500K :      500 KiB
./test_strutils --size 0.50MiB
0.50MiB :              5120000 :     4.9M :      4.9 MiB

New code:
./test_strutils --size 0.5MiB
0.5MiB :               524288 :     512K :      512 KiB
./test_strutils --size 0.50MiB
       0.50MiB :               524288 :     512K :      512 KiB

Note that the new implementation also does not use float points,
because we need to support PiB and so on... it seems good enough for
things like:

        ./test_strutils --size 7.13G
                7.13G :           7656104581 :     7.1G :      7.1 GiB
        ./test_strutils --size 7.16G
                7.16G :           7690675814 :     7.2G :      7.2 GiB

to avoid situation where cfdisk creates partition with completely
crazy numbers.

Addresses: https://github.com/karelzak/util-linux/issues/782
Signed-off-by: Karel Zak <kzak@redhat.com>
lib/strutils.c

index 327bf37bcf3549159cd2b697e6679bd5a5a61cd7..8076574c5d83f4f19c3d3294d93ce48611fcb9cd 100644 (file)
@@ -162,17 +162,39 @@ check_suffix:
        if (power)
                *power = pwr;
        if (frac && pwr) {
-               int zeros_in_pwr = frac_zeros % 3;
-               int frac_pwr = pwr - (frac_zeros / 3) - 1;
-               uintmax_t y = frac * (zeros_in_pwr == 0 ? 100 :
-                                     zeros_in_pwr == 1 ?  10 : 1);
+               int i;
+               uintmax_t frac_div = 10, frac_poz = 1, frac_base = 1;
 
-               if (frac_pwr < 0) {
-                       rc = -EINVAL;
-                       goto err;
-               }
-               do_scale_by_power(&y, base, frac_pwr);
-               x += y;
+               /* mega, giga, ... */
+               do_scale_by_power(&frac_base, base, pwr);
+
+               /* maximal divisor for last digit (e.g. for 0.05 is
+                * frac_div=100, for 0.054 is frac_div=1000, etc.)
+                */
+               while (frac_div < frac)
+                       frac_div *= 10;
+
+               /* 'frac' is without zeros (5 means 0.5 as well as 0.05) */
+               for (i = 0; i < frac_zeros; i++)
+                       frac_div *= 10;
+
+               /*
+                * Go backwardly from last digit and add to result what the
+                * digit represents in the frac_base. For example 0.25G
+                *
+                *  5 means 1GiB / (100/5)
+                *  2 means 1GiB / (10/2)
+                */
+               do {
+                       unsigned int seg = frac % 10;            /* last digit of the frac */
+                       uintmax_t seg_div = frac_div / frac_poz; /* what represents the segment 1000, 100, .. */
+
+                       frac /= 10;     /* remove last digit from frac */
+                       frac_poz *= 10;
+
+                       if (seg)
+                               x += frac_base / (seg_div / seg);
+               } while (frac);
        }
 done:
        *res = x;