Skip to content

Commit 508ee8c

Browse files
Merge pull request #163 from tkfu/feat/use-rational-arithmetic
Fix issues with floating point precision and rounding
2 parents 89c7461 + 1713cc1 commit 508ee8c

File tree

6 files changed

+266
-50
lines changed

6 files changed

+266
-50
lines changed

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ LIBBYTESIZE_PKG_CHECK_MODULES([PCRE2], [libpcre2-8])
4646

4747
AC_CHECK_LIB(gmp, __gmpz_init)
4848

49-
AC_CHECK_HEADERS([langinfo.h gmp.h mpfr.h stdint.h stdbool.h stdarg.h string.h stdio.h ctype.h],
49+
AC_CHECK_HEADERS([langinfo.h gmp.h stdint.h stdbool.h stdarg.h string.h stdio.h ctype.h],
5050
[],
5151
[LIBBYTESIZE_SOFT_FAILURE([Header file $ac_header not found.])],
5252
[])

po/libbytesize.pot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: libbytesize 2.12\n"
1010
"Report-Msgid-Bugs-To: vtrefny@redhat.com\n"
11-
"POT-Creation-Date: 2025-12-04 12:49+0100\n"
11+
"POT-Creation-Date: 2026-01-08 06:02-0800\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <LL@li.org>\n"

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ LDADD = $(LIBINTL)
33

44
lib_LTLIBRARIES = libbytesize.la
55
libbytesize_la_CFLAGS = -Wall -Wextra -Werror -Wno-overflow -D_GNU_SOURCE
6-
libbytesize_la_LIBADD = -lgmp -lmpfr $(PCRE2_LIBS)
6+
libbytesize_la_LIBADD = -lgmp $(PCRE2_LIBS)
77
libbytesize_la_LDFLAGS = -version-info 1:0:0
88
libbytesize_la_SOURCES = bs_size.c bs_size.h gettext.h
99

src/bs_size.c

Lines changed: 136 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#include <gmp.h>
2-
#include <mpfr.h>
32
#include <langinfo.h>
43
#include <stdarg.h>
54
#include <stdio.h>
5+
#include <stdlib.h>
66
#include <inttypes.h>
77
#include <string.h>
88
#include <ctype.h>
@@ -224,53 +224,58 @@ static void strstrip(char *str) {
224224
str[i-begin] = '\0';
225225
}
226226

227-
static bool multiply_size_by_unit (mpfr_t size, char *unit_str) {
227+
static bool multiply_size_by_unit (mpq_t size, char *unit_str) {
228228
BSBunit bunit = BS_BUNIT_UNDEF;
229229
BSDunit dunit = BS_DUNIT_UNDEF;
230230
uint64_t pwr = 0;
231-
mpfr_t dec_mul;
231+
mpq_t dec_mul;
232+
mpz_t pow_1000;
232233
size_t unit_str_len = 0;
233234

234235
unit_str_len = strlen (unit_str);
235236

236237
for (bunit=BS_BUNIT_B; bunit < BS_BUNIT_UNDEF; bunit++)
237238
if (strncasecmp (unit_str, b_units[bunit-BS_BUNIT_B], unit_str_len) == 0) {
238239
pwr = (uint64_t) bunit - BS_BUNIT_B;
239-
mpfr_mul_2exp (size, size, 10 * pwr, MPFR_RNDN);
240+
mpz_mul_2exp (mpq_numref (size), mpq_numref (size), 10 * pwr);
240241
return true;
241242
}
242243

243-
mpfr_init2 (dec_mul, BS_FLOAT_PREC_BITS);
244-
mpfr_set_ui (dec_mul, 1000, MPFR_RNDN);
244+
mpq_init (dec_mul);
245+
mpz_init (pow_1000);
245246
for (dunit=BS_DUNIT_B; dunit < BS_DUNIT_UNDEF; dunit++)
246247
if (strncasecmp (unit_str, d_units[dunit-BS_DUNIT_B], unit_str_len) == 0) {
247248
pwr = (uint64_t) (dunit - BS_DUNIT_B);
248-
mpfr_pow_ui (dec_mul, dec_mul, pwr, MPFR_RNDN);
249-
mpfr_mul (size, size, dec_mul, MPFR_RNDN);
250-
mpfr_clear (dec_mul);
249+
mpz_ui_pow_ui (pow_1000, 1000, pwr);
250+
mpq_set_z (dec_mul, pow_1000);
251+
mpq_mul (size, size, dec_mul);
252+
mpz_clear (pow_1000);
253+
mpq_clear (dec_mul);
251254
return true;
252255
}
253256

254-
/* not found among the binary and decimal units, let's try their translated
255-
versions */
256257
for (bunit=BS_BUNIT_B; bunit < BS_BUNIT_UNDEF; bunit++)
257258
if (strncasecmp (unit_str, _(b_units[bunit-BS_BUNIT_B]), unit_str_len) == 0) {
258259
pwr = (uint64_t) bunit - BS_BUNIT_B;
259-
mpfr_mul_2exp (size, size, 10 * pwr, MPFR_RNDN);
260+
mpz_mul_2exp (mpq_numref (size), mpq_numref (size), 10 * pwr);
261+
mpz_clear (pow_1000);
262+
mpq_clear (dec_mul);
260263
return true;
261264
}
262265

263-
mpfr_init2 (dec_mul, BS_FLOAT_PREC_BITS);
264-
mpfr_set_ui (dec_mul, 1000, MPFR_RNDN);
265266
for (dunit=BS_DUNIT_B; dunit < BS_DUNIT_UNDEF; dunit++)
266267
if (strncasecmp (unit_str, _(d_units[dunit-BS_DUNIT_B]), unit_str_len) == 0) {
267268
pwr = (uint64_t) (dunit - BS_DUNIT_B);
268-
mpfr_pow_ui (dec_mul, dec_mul, pwr, MPFR_RNDN);
269-
mpfr_mul (size, size, dec_mul, MPFR_RNDN);
270-
mpfr_clear (dec_mul);
269+
mpz_ui_pow_ui (pow_1000, 1000, pwr);
270+
mpq_set_z (dec_mul, pow_1000);
271+
mpq_mul (size, size, dec_mul);
272+
mpz_clear (pow_1000);
273+
mpq_clear (dec_mul);
271274
return true;
272275
}
273276

277+
mpz_clear (pow_1000);
278+
mpq_clear (dec_mul);
274279
return false;
275280
}
276281

@@ -436,10 +441,10 @@ BSSize bs_size_new_from_bytes (uint64_t bytes, int sgn) {
436441
*/
437442
BSSize bs_size_new_from_str (const char *size_str, BSError **error) {
438443
char const * const pattern = "^\\s* # white space \n" \
439-
"(?P<numeric> # the numeric part consists of three parts, below \n" \
440-
" (-|\\+)? # optional sign character \n" \
441-
" (?P<base>([0-9\\.%s]+)) # base \n" \
442-
" (?P<exp>(e|E)(-|\\+)?[0-9]+)?) # exponent \n" \
444+
"(?P<sign>(-|\\+)?) # optional sign character \n" \
445+
"(?P<int_part>[0-9]*) # integer part \n" \
446+
"(?:%s(?P<frac_part>[0-9]*))? # optional fractional part \n" \
447+
"(?:(?P<exp_sep>[eE])(?P<exp_sign>(-|\\+)?)(?P<exp_val>[0-9]+))? # optional exponent \n" \
443448
"\\s* # white space \n" \
444449
"(?P<rest>[^\\s]*)\\s*$ # unit specification";
445450
char *real_pattern = NULL;
@@ -451,19 +456,25 @@ BSSize bs_size_new_from_str (const char *size_str, BSError **error) {
451456
int str_count = 0;
452457
const char *radix_char = NULL;
453458
char *loc_size_str = NULL;
454-
mpf_t parsed_size;
455-
mpfr_t size;
459+
mpq_t size;
456460
int status = 0;
457461
BSSize ret = NULL;
458462
PCRE2_UCHAR *substring = NULL;
459463
PCRE2_SIZE substring_len = 0;
460464
PCRE2_UCHAR error_buffer[ERROR_BUFFER_LEN];
465+
int sign = 1;
466+
long exp_val = 0;
467+
int exp_sign = 1;
468+
mpz_t numerator, denominator, int_part, frac_part, pow_10;
469+
size_t frac_digits = 0;
470+
bool has_int_digits = false;
471+
bool has_frac_digits = false;
461472

462473
radix_char = nl_langinfo (RADIXCHAR);
463474
if (strncmp (radix_char, ".", 1) != 0)
464475
real_pattern = strdup_printf (pattern, radix_char);
465476
else
466-
real_pattern = strdup_printf (pattern, "");
477+
real_pattern = strdup_printf (pattern, "\\.");
467478

468479
regex = pcre2_compile ((PCRE2_SPTR) real_pattern, PCRE2_ZERO_TERMINATED, PCRE2_EXTENDED, &errorcode, &erroffset, NULL);
469480
free (real_pattern);
@@ -509,32 +520,111 @@ BSSize bs_size_new_from_str (const char *size_str, BSError **error) {
509520
return NULL;
510521
}
511522

512-
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "numeric", &substring, &substring_len);
513-
if (status < 0 || substring_len < 1) {
514-
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
515-
pcre2_match_data_free (match_data);
516-
pcre2_code_free (regex);
517-
free (loc_size_str);
518-
return NULL;
523+
mpq_init (size);
524+
mpz_init (numerator);
525+
mpz_init (denominator);
526+
mpz_init (int_part);
527+
mpz_init (frac_part);
528+
mpz_init (pow_10);
529+
530+
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "sign", &substring, &substring_len);
531+
if (status >= 0 && substring_len > 0 && substring[0] == '-')
532+
sign = -1;
533+
if (status >= 0)
534+
pcre2_substring_free (substring);
535+
536+
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "int_part", &substring, &substring_len);
537+
if (status >= 0 && substring_len > 0) {
538+
has_int_digits = true;
539+
status = mpz_set_str (int_part, (const char *) substring, 10);
540+
pcre2_substring_free (substring);
541+
if (status != 0) {
542+
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
543+
mpz_clears (numerator, denominator, int_part, frac_part, pow_10, NULL);
544+
mpq_clear (size);
545+
pcre2_match_data_free (match_data);
546+
pcre2_code_free (regex);
547+
free (loc_size_str);
548+
return NULL;
549+
}
550+
} else {
551+
mpz_set_ui (int_part, 0);
552+
if (status >= 0)
553+
pcre2_substring_free (substring);
519554
}
520555

521-
/* parse the number using GMP because it knows how to handle localization
522-
much better than MPFR */
523-
mpf_init2 (parsed_size, BS_FLOAT_PREC_BITS);
524-
status = mpf_set_str (parsed_size, *substring == '+' ? (const char *) substring+1 : (const char *) substring, 10);
525-
pcre2_substring_free (substring);
526-
if (status != 0) {
556+
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "frac_part", &substring, &substring_len);
557+
if (status >= 0 && substring_len > 0) {
558+
has_frac_digits = true;
559+
frac_digits = substring_len;
560+
status = mpz_set_str (frac_part, (const char *) substring, 10);
561+
pcre2_substring_free (substring);
562+
if (status != 0) {
563+
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
564+
mpz_clears (numerator, denominator, int_part, frac_part, pow_10, NULL);
565+
mpq_clear (size);
566+
pcre2_match_data_free (match_data);
567+
pcre2_code_free (regex);
568+
free (loc_size_str);
569+
return NULL;
570+
}
571+
} else {
572+
mpz_set_ui (frac_part, 0);
573+
frac_digits = 0;
574+
if (status >= 0)
575+
pcre2_substring_free (substring);
576+
}
577+
578+
/* Validate: we must have at least one digit in int_part or frac_part */
579+
if (!has_int_digits && !has_frac_digits) {
527580
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
581+
mpz_clears (numerator, denominator, int_part, frac_part, pow_10, NULL);
582+
mpq_clear (size);
528583
pcre2_match_data_free (match_data);
529584
pcre2_code_free (regex);
530585
free (loc_size_str);
531-
mpf_clear (parsed_size);
532586
return NULL;
533587
}
534-
/* but use MPFR from now on because GMP thinks 0.1*1000 = 99 */
535-
mpfr_init2 (size, BS_FLOAT_PREC_BITS);
536-
mpfr_set_f (size, parsed_size, MPFR_RNDN);
537-
mpf_clear (parsed_size);
588+
589+
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "exp_val", &substring, &substring_len);
590+
if (status >= 0 && substring_len > 0) {
591+
exp_val = strtol ((const char *) substring, NULL, 10);
592+
pcre2_substring_free (substring);
593+
594+
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "exp_sign", &substring, &substring_len);
595+
if (status >= 0 && substring_len > 0 && substring[0] == '-')
596+
exp_sign = -1;
597+
if (status >= 0)
598+
pcre2_substring_free (substring);
599+
}
600+
601+
mpz_ui_pow_ui (pow_10, 10, frac_digits);
602+
mpz_set (denominator, pow_10);
603+
mpz_mul (numerator, int_part, pow_10);
604+
mpz_add (numerator, numerator, frac_part);
605+
606+
if (exp_val != 0) {
607+
long adjusted_exp = exp_val;
608+
if (exp_sign == -1)
609+
adjusted_exp = -adjusted_exp;
610+
611+
if (adjusted_exp > 0) {
612+
mpz_ui_pow_ui (pow_10, 10, adjusted_exp);
613+
mpz_mul (numerator, numerator, pow_10);
614+
} else if (adjusted_exp < 0) {
615+
mpz_ui_pow_ui (pow_10, 10, -adjusted_exp);
616+
mpz_mul (denominator, denominator, pow_10);
617+
}
618+
}
619+
620+
if (sign == -1)
621+
mpz_neg (numerator, numerator);
622+
623+
mpq_set_num (size, numerator);
624+
mpq_set_den (size, denominator);
625+
mpq_canonicalize (size);
626+
627+
mpz_clears (numerator, denominator, int_part, frac_part, pow_10, NULL);
538628

539629
status = pcre2_substring_get_byname (match_data, (PCRE2_SPTR) "rest", &substring, &substring_len);
540630
if ((status >= 0) && strncmp ((const char *) substring, "", 1) != 0) {
@@ -545,7 +635,7 @@ BSSize bs_size_new_from_str (const char *size_str, BSError **error) {
545635
pcre2_match_data_free (match_data);
546636
pcre2_code_free (regex);
547637
free (loc_size_str);
548-
mpfr_clear (size);
638+
mpq_clear (size);
549639
return NULL;
550640
}
551641
}
@@ -554,10 +644,11 @@ BSSize bs_size_new_from_str (const char *size_str, BSError **error) {
554644
pcre2_code_free (regex);
555645

556646
ret = bs_size_new ();
557-
mpfr_get_z (ret->bytes, size, MPFR_RNDZ);
647+
/* Rational to int, round towards zero for preserving previous behaviour */
648+
mpz_tdiv_q (ret->bytes, mpq_numref (size), mpq_denref (size));
558649

559650
free (loc_size_str);
560-
mpfr_clear (size);
651+
mpq_clear (size);
561652

562653
return ret;
563654
}

src/bytesize.pc.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ URL: https://github.com/storaged-project/libbytesize
99
Version: @VERSION@
1010
Cflags: -I${includedir}/bytesize
1111
Libs: -lbytesize
12-
Libs.private: -lgmp -lmpfr
12+
Libs.private: -lgmp

0 commit comments

Comments
 (0)