#include <gmp.h>
#include <mpfr.h>
#include <langinfo.h>
#include <stdarg.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <pcre.h>
#include "bs_size.h"
#include "gettext.h"
#define _(String) gettext (String)
#define N_(String) String
/**
* SECTION: bs_size
* @title: BSSize
* @short_description: a class facilitating work with sizes in bytes
* @include: bs_size.h
*
* #BSSize is a type that facilitates work with sizes in bytes by providing
* functions/methods that are required for parsing users input when entering
* size, showing size in nice human-readable format, storing sizes bigger than
* %UINT64_MAX and doing calculations with sizes without loss of
* precision/information. The class is able to hold negative sizes and do
* operations on/with them, but some of the (division and multiplication)
* operations simply ignore the signs of the operands (check the documentation).
*
* The reason why some functions take or return a float as a string instead of a
* float directly is because a string "0.3" can be translated into 0.3 with
* appropriate precision while 0.3 as float is probably something like
* 0.294343... with unknown precision.
*
*/
/***************
* STATIC DATA *
***************/
static char const * const b_units[BS_BUNIT_UNDEF] = {
/* TRANSLATORS: 'B' for bytes */
N_("B"),
/* TRANSLATORS: abbreviation for kibibyte, 2**10 bytes */
N_("KiB"),
/* TRANSLATORS: abbreviation for mebibyte, 2**20 bytes */
N_("MiB"),
/* TRANSLATORS: abbreviation for gibibyte, 2**30 bytes */
N_("GiB"),
/* TRANSLATORS: abbreviation for tebibyte, 2**40 bytes */
N_("TiB"),
/* TRANSLATORS: abbreviation for pebibyte, 2**50 bytes */
N_("PiB"),
/* TRANSLATORS: abbreviation for exbibyte, 2**60 bytes */
N_("EiB"),
/* TRANSLATORS: abbreviation for zebibyte, 2**70 bytes */
N_("ZiB"),
/* TRANSLATORS: abbreviation for yobibyte, 2**80 bytes */
N_("YiB")
};
static char const * const d_units[BS_DUNIT_UNDEF] = {
/* TRANSLATORS: 'B' for bytes */
N_("B"),
/* TRANSLATORS: abbreviation for kilobyte, 10**3 bytes */
N_("KB"),
/* TRANSLATORS: abbreviation for megabyte, 10**6 bytes */
N_("MB"),
/* TRANSLATORS: abbreviation for gigabyte, 10**9 bytes */
N_("GB"),
/* TRANSLATORS: abbreviation for terabyte, 10**12 bytes */
N_("TB"),
/* TRANSLATORS: abbreviation for petabyte, 10**15 bytes */
N_("PB"),
/* TRANSLATORS: abbreviation for exabyte, 10**18 bytes */
N_("EB"),
/* TRANSLATORS: abbreviation for zettabyte, 10**21 bytes */
N_("ZB"),
/* TRANSLATORS: abbreviation for yottabyte, 10**24 bytes */
N_("YB")
};
/****************************
* STRUCT DEFINITIONS *
****************************/
/**
* BSSize:
*
* The BSSize struct contains only private fields and should not be directly
* accessed.
*/
struct _BSSize {
mpz_t bytes;
};
/********************
* HELPER FUNCTIONS *
********************/
static void bs_size_init (BSSize size) {
/* let's start with 64 bits of space */
mpz_init2 (size->bytes, (mp_bitcnt_t) 64);
}
static char *strdup_printf (const char *fmt, ...) {
int num = 0;
char *ret = NULL;
va_list ap;
va_start (ap, fmt);
num = vasprintf (&ret, fmt, ap);
va_end (ap);
if (num <= 0)
/* make sure we return NULL on error */
ret = NULL;
return ret;
}
/**
* replace_char_with_str: (skip)
*
* Replaces all apperances of @char in @str with @new.
*/
static char *replace_char_with_str (const char *str, char orig, const char *new) {
uint64_t offset = 0;
uint64_t i = 0;
uint64_t j = 0;
char *ret = NULL;
const char *next = NULL;
uint64_t count = 0;
if (!str)
return NULL;
next = str;
for (next=strchr (next, orig); next; count++, next=strchr (++next, orig));
/* allocate space for the string [strlen(str)] with the char replaced by the
string [strlen(new) - 1] $count times and a \0 byte at the end [ + 1] */
ret = malloc (sizeof(char) * (strlen(str) + (strlen(new) - 1) * count + 1));
for (i=0; str[i]; i++) {
if (str[i] == orig)
for (j=0; new[j]; j++) {
ret[i+offset] = new[j];
if (new[j+1])
/* something more to copy over */
offset++;
}
else
ret[i+offset] = str[i];
}
ret[i+offset] = '\0';
return ret;
}
/**
* replace_str_with_str: (skip)
*
* Replaces the first appearance of @orig in @str with @new.
*/
static char *replace_str_with_str (const char *str, const char *orig, const char *new) {
char *pos = NULL;
int str_len = 0;
int orig_len = 0;
int new_len = 0;
char *ret = NULL;
int ret_size = 0;
char *dest = NULL;
pos = strstr (str, orig);
if (!pos)
/* nothing to do, just return a copy */
return strdup (str);
str_len = strlen (str);
orig_len = strlen (orig);
new_len = strlen (new);
ret_size = str_len + new_len - orig_len + 1;
ret = malloc (sizeof(char) * ret_size);
memset (ret, 0, ret_size);
memcpy (ret, str, pos - str);
dest = ret + (pos - str);
memcpy (dest, new, new_len);
dest = dest + new_len;
memcpy (dest, pos + orig_len, str_len - (pos - str) - orig_len);
return ret;
}
/**
* strstrip: (skip)
*
* Strips leading and trailing whitespace from the string (**IN-PLACE**)
*/
static void strstrip(char *str) {
int i = 0;
int begin = 0;
int end = strlen(str) - 1;
while (isspace(str[begin]))
begin++;
while ((end >= begin) && isspace(str[end]))
end--;
for (i=begin; i <= end; i++)
str[i - begin] = str[i];
str[i-begin] = '\0';
}
static bool multiply_size_by_unit (mpfr_t size, char *unit_str) {
BSBunit bunit = BS_BUNIT_UNDEF;
BSDunit dunit = BS_DUNIT_UNDEF;
uint64_t pwr = 0;
mpfr_t dec_mul;
size_t unit_str_len = 0;
unit_str_len = strlen (unit_str);
for (bunit=BS_BUNIT_B; bunit < BS_BUNIT_UNDEF; bunit++)
if (strncasecmp (unit_str, b_units[bunit-BS_BUNIT_B], unit_str_len) == 0) {
pwr = (uint64_t) bunit - BS_BUNIT_B;
mpfr_mul_2exp (size, size, 10 * pwr, MPFR_RNDN);
return true;
}
mpfr_init2 (dec_mul, BS_FLOAT_PREC_BITS);
mpfr_set_ui (dec_mul, 1000, MPFR_RNDN);
for (dunit=BS_DUNIT_B; dunit < BS_DUNIT_UNDEF; dunit++)
if (strncasecmp (unit_str, d_units[dunit-BS_DUNIT_B], unit_str_len) == 0) {
pwr = (uint64_t) (dunit - BS_DUNIT_B);
mpfr_pow_ui (dec_mul, dec_mul, pwr, MPFR_RNDN);
mpfr_mul (size, size, dec_mul, MPFR_RNDN);
mpfr_clear (dec_mul);
return true;
}
/* not found among the binary and decimal units, let's try their translated
verions */
for (bunit=BS_BUNIT_B; bunit < BS_BUNIT_UNDEF; bunit++)
if (strncasecmp (unit_str, _(b_units[bunit-BS_BUNIT_B]), unit_str_len) == 0) {
pwr = (uint64_t) bunit - BS_BUNIT_B;
mpfr_mul_2exp (size, size, 10 * pwr, MPFR_RNDN);
return true;
}
mpfr_init2 (dec_mul, BS_FLOAT_PREC_BITS);
mpfr_set_ui (dec_mul, 1000, MPFR_RNDN);
for (dunit=BS_DUNIT_B; dunit < BS_DUNIT_UNDEF; dunit++)
if (strncasecmp (unit_str, _(d_units[dunit-BS_DUNIT_B]), unit_str_len) == 0) {
pwr = (uint64_t) (dunit - BS_DUNIT_B);
mpfr_pow_ui (dec_mul, dec_mul, pwr, MPFR_RNDN);
mpfr_mul (size, size, dec_mul, MPFR_RNDN);
mpfr_clear (dec_mul);
return true;
}
return false;
}
/**
* set_error: (skip)
*
* Sets @error to @code and @msg (if not %NULL). **TAKES OVER @msg.**
*/
static void set_error (BSError **error, BSErrorCode code, char *msg) {
*error = (BSError *) malloc (sizeof(BSError));
(*error)->code = code;
(*error)->msg = msg;
return;
}
typedef void (*MpzOp) (mpz_t ROP, const mpz_t OP1, unsigned long int OP2);
static void do_64bit_add_sub (MpzOp op, mpz_t rop, const mpz_t op1, uint64_t op2) {
uint64_t i = 0;
uint64_t div = 0;
uint64_t mod = 0;
/* small enough to just work */
if (op2 < (uint64_t) ULONG_MAX) {
op (rop, op1, (unsigned long int) op2);
return;
}
mpz_set (rop, op1);
div = op2 / (uint64_t) ULONG_MAX;
mod = op2 % (uint64_t) ULONG_MAX;
for (i=0; i < div; i++)
op (rop, rop, (unsigned long int) ULONG_MAX);
op (rop, rop, (unsigned long int) mod);
}
static void mul_64bit (mpz_t rop, const mpz_t op1, uint64_t op2) {
uint64_t i = 0;
uint64_t div = 0;
uint64_t mod = 0;
mpz_t aux;
mpz_t res;
/* small enough to just work */
if (op2 < (uint64_t) ULONG_MAX) {
mpz_mul_ui (rop, op1, (unsigned long int) op2);
return;
}
mpz_init2 (aux, (mp_bitcnt_t) 64);
mpz_init2 (res, (mp_bitcnt_t) 64);
mpz_set_ui (res, 0);
div = op2 / (uint64_t) ULONG_MAX;
mod = op2 % (uint64_t) ULONG_MAX;
for (i=0; i < div; i++) {
mpz_mul_ui (aux, op1, (unsigned long int) ULONG_MAX);
mpz_add (res, res, aux);
}
mpz_mul_ui (aux, op1, (unsigned long int) mod);
mpz_add (res, res, aux);
mpz_set (rop, res);
mpz_clear (aux);
mpz_clear (res);
}
/***************
* DESTRUCTORS *
* *************/
/**
* bs_size_free:
*
* Clears @size and frees the allocated resources.
*/
void bs_size_free (BSSize size) {
if (size) {
mpz_clear (size->bytes);
free (size);
}
return;
}
/**
* bs_clear_error:
*
* Clears @error and frees the allocated resources.
*/
void bs_clear_error (BSError **error) {
if (error && *error) {
free ((*error)->msg);
free (*error);
*error = NULL;
}
return;
}
/****************
* CONSTRUCTORS *
****************/
/**
* bs_size_new: (constructor)
*
* Creates a new #BSSize instance initialized to 0.
*
* Returns: a new #BSSize initialized to 0.
*/
BSSize bs_size_new (void) {
BSSize ret = (BSSize) malloc (sizeof(struct _BSSize));
bs_size_init (ret);
return ret;
}
/**
* bs_size_new_from_bytes: (constructor)
* @bytes: number of bytes
* @sgn: sign of the size -- if being -1, the size is initialized to
* -@bytes, other values are ignored
*
* Creates a new #BSSize instance.
*
* Returns: a new #BSSize
*/
BSSize bs_size_new_from_bytes (uint64_t bytes, int sgn) {
char *num_str = NULL;
BSSize ret = bs_size_new ();
int ok = 0;
ok = asprintf (&num_str, "%"PRIu64, bytes);
if (ok == -1)
/* probably cannot allocate memory, there's nothing more we can do */
return ret;
mpz_set_str (ret->bytes, num_str, 10);
free (num_str);
if (sgn == -1)
mpz_neg (ret->bytes, ret->bytes);
return ret;
}
/**
* bs_size_new_from_str: (constructor)
* @size_str: string representing the size as a number and an optional unit
* (e.g. "1 GiB")
*
* Creates a new #BSSize instance.
*
* Returns: a new #BSSize
*/
BSSize bs_size_new_from_str (const char *size_str, BSError **error) {
char const * const pattern = "^\\s* # white space \n" \
"(?P<numeric> # the numeric part consists of three parts, below \n" \
" (-|\\+)? # optional sign character \n" \
" (?P<base>([0-9\\.%s]+)) # base \n" \
" (?P<exp>(e|E)(-|\\+)[0-9]+)?) # exponent \n" \
"\\s* # white space \n" \
"(?P<rest>[^\\s]*)\\s*$ # unit specification";
char *real_pattern = NULL;
pcre *regex = NULL;
const char *error_msg = NULL;
int erroffset;
int str_len = 0;
int ovector[30]; /* should be a multiple of 3 */
int str_count = 0;
char *num_str = NULL;
const char *radix_char = NULL;
char *loc_size_str = NULL;
mpf_t parsed_size;
mpfr_t size;
int status = 0;
char *unit_str = NULL;
BSSize ret = NULL;
radix_char = nl_langinfo (RADIXCHAR);
if (strncmp (radix_char, ".", 1) != 0)
real_pattern = strdup_printf (pattern, radix_char);
else
real_pattern = strdup_printf (pattern, "");
regex = pcre_compile (real_pattern, PCRE_EXTENDED, &error_msg, &erroffset, NULL);
free (real_pattern);
if (!regex) {
/* TODO: populate error */
return NULL;
}
loc_size_str = replace_char_with_str (size_str, '.', radix_char);
str_len = strlen (loc_size_str);
str_count = pcre_exec (regex, NULL, loc_size_str, str_len,
0, 0, ovector, 30);
if (str_count < 0) {
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
pcre_free (regex);
free (loc_size_str);
return NULL;
}
status = pcre_get_named_substring (regex, loc_size_str, ovector, str_count, "numeric", (const char **)&num_str);
if (status < 0) {
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
pcre_free (regex);
free (loc_size_str);
return NULL;
}
/* parse the number using GMP because it knows how to handle localization
much better than MPFR */
mpf_init2 (parsed_size, BS_FLOAT_PREC_BITS);
status = mpf_set_str (parsed_size, *num_str == '+' ? num_str+1 : num_str, 10);
free (num_str);
if (status != 0) {
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to parse size spec: %s", size_str));
pcre_free (regex);
free (loc_size_str);
mpf_clear (parsed_size);
return NULL;
}
/* but use MPFR from now on because GMP thinks 0.1*1000 = 99 */
mpfr_init2 (size, BS_FLOAT_PREC_BITS);
mpfr_set_f (size, parsed_size, MPFR_RNDN);
mpf_clear (parsed_size);
status = pcre_get_named_substring (regex, loc_size_str, ovector, str_count, "rest", (const char **)&unit_str);
if ((status >= 0) && strncmp (unit_str, "", 1) != 0) {
strstrip (unit_str);
if (!multiply_size_by_unit (size, unit_str)) {
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("Failed to recognize unit from the spec: %s", size_str));
free (unit_str);
pcre_free (regex);
free (loc_size_str);
mpfr_clear (size);
return NULL;
}
}
free (unit_str);
pcre_free (regex);
ret = bs_size_new ();
mpfr_get_z (ret->bytes, size, MPFR_RNDZ);
free (loc_size_str);
mpfr_clear (size);
return ret;
}
/**
* bs_size_new_from_size: (constructor)
* @size: the size to create a new instance from (a copy of)
*
* Creates a new instance of #BSSize.
*
* Returns: (transfer full): a new #BSSize instance which is copy of @size.
*/
BSSize bs_size_new_from_size (const BSSize size) {
BSSize ret = NULL;
ret = bs_size_new ();
mpz_set (ret->bytes, size->bytes);
return ret;
}
/*****************
* QUERY METHODS *
*****************/
/**
* bs_size_get_bytes:
* @sgn: (allow-none) (out): sign of the @size - -1, 0 or 1 for negative, zero or positive
* size respectively
*
* Get the number of bytes of the @size.
*
* Returns: the @size in a number of bytes
*/
uint64_t bs_size_get_bytes (const BSSize size, int *sgn, BSError **error) {
char *num_str = NULL;
mpz_t max;
uint64_t ret = 0;
int ok = 0;
mpz_init2 (max, (mp_bitcnt_t) 64);
ok = asprintf (&num_str, "%"PRIu64, UINT64_MAX);
if (ok == -1) {
/* we probably cannot allocate memory so we are doomed */
set_error (error, BS_ERROR_FAIL, strdup("Failed to allocate memory"));
mpz_clear (max);
return 0;
}
mpz_set_str (max, num_str, 10);
free (num_str);
if (mpz_cmp (size->bytes, max) > 0) {
set_error (error, BS_ERROR_OVER, strdup("The size is too big, cannot be returned as a 64bit number of bytes"));
return 0;
}
mpz_clear (max);
if (sgn)
*sgn = mpz_sgn (size->bytes);
if (mpz_cmp_ui (size->bytes, UINT64_MAX) <= 0)
return (uint64_t) mpz_get_ui (size->bytes);
else {
num_str = bs_size_get_bytes_str (size);
ret = strtoull (num_str, NULL, 10);
free (num_str);
return ret;
}
}
/**
* bs_size_sgn:
*
* Get the sign of the size.
*
* Returns: -1, 0 or 1 if @size is negative, zero or positive, respectively
*/
int bs_size_sgn (const BSSize size) {
return mpz_sgn (size->bytes);
}
/**
* bs_size_get_bytes_str:
*
* Get the number of bytes in @size as a string. This way, the caller doesn't
* have to care about the limitations of some particular integer type.
*
* Returns: (transfer full): the string representing the @size as a number of bytes.
*/
char* bs_size_get_bytes_str (const BSSize size) {
return mpz_get_str (NULL, 10, size->bytes);
}
/**
* bs_size_convert_to:
* @unit: the unit to convert @size to
*
* Get the @size converted to @unit as a string representing a floating-point
* number.
*
* Returns: (transfer full): a string representing the floating-point number
* that equals to @size converted to @unit
*/
char* bs_size_convert_to (const BSSize size, BSUnit unit, BSError **error) {
BSBunit b_unit = BS_BUNIT_B;
BSDunit d_unit = BS_DUNIT_B;
mpf_t divisor;
mpf_t result;
bool found_match = false;
char *ret = NULL;
mpf_init2 (divisor, BS_FLOAT_PREC_BITS);
for (b_unit = BS_BUNIT_B; !found_match && b_unit != BS_BUNIT_UNDEF; b_unit++) {
if (unit.bunit == b_unit) {
found_match = true;
mpf_set_ui (divisor, 1);
mpf_mul_2exp (divisor, divisor, 10 * (b_unit - BS_BUNIT_B));
}
}
for (d_unit = BS_DUNIT_B; !found_match && d_unit != BS_DUNIT_UNDEF; d_unit++) {
if (unit.dunit == d_unit) {
found_match = true;
mpf_set_ui (divisor, 1000);
mpf_pow_ui (divisor, divisor, (d_unit - BS_DUNIT_B));
}
}
if (!found_match) {
set_error (error, BS_ERROR_INVALID_SPEC, "Invalid unit spec given");
mpf_clear (divisor);
return NULL;
}
mpf_init2 (result, BS_FLOAT_PREC_BITS);
mpf_set_z (result, size->bytes);
mpf_div (result, result, divisor);
gmp_asprintf (&ret, "%.*Fg", BS_FLOAT_PREC_BITS/3, result);
mpf_clears (divisor, result, NULL);
return ret;
}
/**
* bs_size_human_readable:
* @min_unit: the smallest unit the returned representation should use
* @max_places: maximum number of decimal places the representation should use
* @xlate: whether to try to translate the representation or not
*
* Get a human-readable representation of @size.
*
* Returns: (transfer full): a string which is human-readable representation of
* @size according to the restrictions given by the
* other parameters
*/
char* bs_size_human_readable (const BSSize size, BSBunit min_unit, int max_places, bool xlate) {
mpf_t cur_val;
char *num_str = NULL;
char *ret = NULL;
int len = 0;
char *zero = NULL;
char *radix_char = NULL;
int sign = 0;
char *loc_num_str = NULL;
bool at_radix = false;
mpf_init2 (cur_val, BS_FLOAT_PREC_BITS);
mpf_set_z (cur_val, size->bytes);
if (min_unit == BS_BUNIT_UNDEF)
min_unit = BS_BUNIT_B;
sign = mpf_sgn (cur_val);
mpf_abs (cur_val, cur_val);
mpf_div_2exp (cur_val, cur_val, 10 * (min_unit - BS_BUNIT_B));
while ((mpf_cmp_ui (cur_val, 1024) > 0) && min_unit != BS_BUNIT_YiB) {
mpf_div_2exp (cur_val, cur_val, 10);
min_unit++;
}
if (sign == -1)
mpf_neg (cur_val, cur_val);
len = gmp_asprintf (&num_str, "%.*Ff", max_places >= 0 ? max_places : BS_FLOAT_PREC_BITS,
cur_val);
mpf_clear (cur_val);
/* should use the proper radix char according to @xlate */
radix_char = nl_langinfo (RADIXCHAR);
if (!xlate) {
loc_num_str = replace_str_with_str (num_str, radix_char, ".");
free (num_str);
radix_char = ".";
} else
loc_num_str = num_str;
/* remove trailing zeros and the radix char */
/* if max_places == 0, there can't be anything trailing */
if (max_places != 0) {
zero = loc_num_str + (len - 1);
while ((zero != loc_num_str) && ((*zero == '0') || (*zero == *radix_char)) && !at_radix) {
at_radix = *zero == *radix_char;
zero--;
}
zero[1] = '\0';
}
ret = strdup_printf ("%s %s", loc_num_str, xlate ? _(b_units[min_unit - BS_BUNIT_B]) : b_units[min_unit - BS_BUNIT_B]);
free (loc_num_str);
return ret;
}
/***************
* ARITHMETIC *
***************/
/**
* bs_size_add:
*
* Add two sizes.
*
* Returns: (transfer full): a new instance of #BSSize which is a sum of @size1 and @size2
*/
BSSize bs_size_add (const BSSize size1, const BSSize size2) {
BSSize ret = bs_size_new ();
mpz_add (ret->bytes, size1->bytes, size2->bytes);
return ret;
}
/**
* bs_size_grow:
*
* Grows @size1 by @size2. IOW, adds @size2 to @size1 in-place (modifying
* @size1).
*
* Basically an in-place variant of bs_size_add().
*
* Returns: (transfer none): @size1 modified by adding @size2 to it
*/
BSSize bs_size_grow (BSSize size1, const BSSize size2) {
mpz_add (size1->bytes, size1->bytes, size2->bytes);
return size1;
}
/**
* bs_size_add_bytes:
*
* Add @bytes to the @size. To add a negative number of bytes use
* bs_size_sub_bytes().
*
* Returns: (transfer full): a new instance of #BSSize which is a sum of @size and @bytes
*/
BSSize bs_size_add_bytes (const BSSize size, uint64_t bytes) {
BSSize ret = bs_size_new ();
do_64bit_add_sub (mpz_add_ui, ret->bytes, size->bytes, bytes);
return ret;
}
/**
* bs_size_grow_bytes:
*
* Grows @size by @bytes. IOW, adds @bytes to @size in-place (modifying @size).
*
* Basically an in-place variant of bs_size_add_bytes().
*
* Returns: (transfer none): @size modified by adding @bytes to it
*/
BSSize bs_size_grow_bytes (BSSize size, const uint64_t bytes) {
do_64bit_add_sub (mpz_add_ui, size->bytes, size->bytes, bytes);
return size;
}
/**
* bs_size_sub:
*
* Subtract @size2 from @size1.
*
* Returns: (transfer full): a new instance of #BSSize which is equals to @size1 - @size2
*/
BSSize bs_size_sub (const BSSize size1, const BSSize size2) {
BSSize ret = bs_size_new ();
mpz_sub (ret->bytes, size1->bytes, size2->bytes);
return ret;
}
/**
* bs_size_shrink:
*
* Shrinks @size1 by @size2. IOW, subtracts @size2 from @size1 in-place
* (modifying @size1).
*
* Basically an in-place variant of bs_size_sub().
*
* Returns: (transfer none): @size1 modified by subtracting @size2 from it
*/
BSSize bs_size_shrink (BSSize size1, const BSSize size2) {
mpz_sub (size1->bytes, size1->bytes, size2->bytes);
return size1;
}
/**
* bs_size_sub_bytes:
*
* Subtract @bytes from the @size. To subtract a negative number of bytes use
* bs_size_add_bytes().
*
* Returns: (transfer full): a new instance of #BSSize which equals to @size - @bytes
*/
BSSize bs_size_sub_bytes (const BSSize size, uint64_t bytes) {
BSSize ret = bs_size_new ();
do_64bit_add_sub (mpz_sub_ui, ret->bytes, size->bytes, bytes);
return ret;
}
/**
* bs_size_shrink_bytes:
*
* Shrinks @size by @bytes. IOW, subtracts @bytes from @size in-place
* (modifying @size). To shrink by a negative number of bytes use
* bs_size_grow_bytes().
*
* Basically an in-place variant of bs_size_sub_bytes().
*
* Returns: (transfer none): @size modified by subtracting @bytes from it
*/
BSSize bs_size_shrink_bytes (BSSize size, uint64_t bytes) {
do_64bit_add_sub (mpz_sub_ui, size->bytes, size->bytes, bytes);
return size;
}
/**
* bs_size_mul_int:
*
* Multiply @size by @times.
*
* Returns: (transfer full): a new instance of #BSSize which equals to @size * @times
*/
BSSize bs_size_mul_int (const BSSize size, uint64_t times) {
BSSize ret = bs_size_new ();
mul_64bit (ret->bytes, size->bytes, times);
return ret;
}
/**
* bs_size_grow_mul_int:
*
* Grow @size @times times. IOW, multiply @size by @times in-place.
*
* Basically an in-place variant of bs_size_mul_int().
*
* Returns: (transfer none): @size modified by growing it @times times
*/
BSSize bs_size_grow_mul_int (BSSize size, uint64_t times) {
mul_64bit (size->bytes, size->bytes, times);
return size;
}
/**
* bs_size_mul_float_str:
*
* Multiply @size by the floating-point number @float_str represents.
*
* Returns: (transfer full): a new #BSSize instance which equals to
* @size * @times_str
*
*/
BSSize bs_size_mul_float_str (const BSSize size, const char *float_str, BSError **error) {
mpf_t op1, op2;
int status = 0;
BSSize ret = NULL;
const char *radix_char = NULL;
char *loc_float_str = NULL;
radix_char = nl_langinfo (RADIXCHAR);
mpf_init2 (op1, BS_FLOAT_PREC_BITS);
mpf_init2 (op2, BS_FLOAT_PREC_BITS);
mpf_set_z (op1, size->bytes);
loc_float_str = replace_char_with_str (float_str, '.', radix_char);
status = mpf_set_str (op2, loc_float_str, 10);
if (status != 0) {
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("'%s' is not a valid floating point number string", loc_float_str));
free (loc_float_str);
mpf_clears (op1, op2, NULL);
return NULL;
}
free (loc_float_str);
mpf_mul (op1, op1, op2);
ret = bs_size_new ();
mpz_set_f (ret->bytes, op1);
mpf_clears (op1, op2, NULL);
return ret;
}
/**
* bs_size_grow_mul_float_str:
*
* Grow @size by the floating-point number @float_str represents times. IOW,
* multiply @size by @float_str in-place.
*
* Basically an in-place variant of bs_size_grow_mul_float_str().
*
* Returns: (transfer none): @size modified by growing it @float_str times.
*/
BSSize bs_size_grow_mul_float_str (BSSize size, const char *float_str, BSError **error) {
mpf_t op1, op2;
int status = 0;
const char *radix_char = NULL;
char *loc_float_str = NULL;
radix_char = nl_langinfo (RADIXCHAR);
mpf_init2 (op1, BS_FLOAT_PREC_BITS);
mpf_init2 (op2, BS_FLOAT_PREC_BITS);
mpf_set_z (op1, size->bytes);
loc_float_str = replace_char_with_str (float_str, '.', radix_char);
status = mpf_set_str (op2, loc_float_str, 10);
if (status != 0) {
set_error (error, BS_ERROR_INVALID_SPEC, strdup_printf ("'%s' is not a valid floating point number string", loc_float_str));
free (loc_float_str);
mpf_clears (op1, op2, NULL);
return NULL;
}
free (loc_float_str);
mpf_mul (op1, op1, op2);
mpz_set_f (size->bytes, op1);
mpf_clears (op1, op2, NULL);
return size;
}
/**
* bs_size_div:
* @sgn: (allow-none) (out): sign of the result
*
* Divide @size1 by @size2. Gives the answer to the question "How many times
* does @size2 fit in @size1?".
*
* Returns: integer number x so that x * @size1 < @size2 and (x+1) * @size1 > @size2
* (IOW, @size1 / @size2 using integer division)
*/
uint64_t bs_size_div (const BSSize size1, const BSSize size2, int *sgn, BSError **error) {
mpz_t result;
uint64_t ret = 0;
if (mpz_cmp_ui (size2->bytes, 0) == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf ("Division by zero"));
return 0;
}
if (sgn)
*sgn = mpz_sgn (size1->bytes) * mpz_sgn (size2->bytes);
mpz_init (result);
mpz_tdiv_q (result, size1->bytes, size2->bytes);
if (mpz_cmp_ui (result, UINT64_MAX) > 0) {
set_error (error, BS_ERROR_OVER, strdup_printf ("The size is too big, cannot be returned as a 64bit number"));
mpz_clear (result);
return 0;
}
ret = (uint64_t) mpz_get_ui (result);
mpz_clear (result);
return ret;
}
/**
* bs_size_div_int:
*
* Divide @size by @divisor. Gives the answer to the question "What is the size
* of each chunk if @size is split into a @divisor number of pieces?"
*
* Note: Due to the limitations of the current implementation the maximum value
* @divisor is ULONG_MAX (which can differ from UINT64_MAX). An error
* (BS_ERROR_OVER) is returned if overflow happens.
*
* Returns: (transfer full): a #BSSize instance x so that x * @divisor = @size,
* rounded to a number of bytes
*/
BSSize bs_size_div_int (const BSSize size, uint64_t divisor, BSError **error) {
BSSize ret = NULL;
if (divisor == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf ("Division by zero"));
return NULL;
} else if (divisor > ULONG_MAX) {
set_error (error, BS_ERROR_OVER, strdup_printf ("Divisor too big, must be less or equal to %lu", ULONG_MAX));
return NULL;
}
ret = bs_size_new ();
mpz_tdiv_q_ui (ret->bytes, size->bytes, divisor);
return ret;
}
/**
* bs_size_shrink_div_int:
*
* Shrink @size by dividing by @divisor. IOW, divide @size by @divisor in-place.
*
* Basically an in-place variant of bs_size_div_int().
*
* Note: Due to the limitations of the current implementation the maximum value
* @divisor is ULONG_MAX (which can differ from UINT64_MAX). An error
* (BS_ERROR_OVER) is returned if overflow happens.
*
* Returns: (transfer none): @size modified by division by @divisor
*/
BSSize bs_size_shrink_div_int (BSSize size, uint64_t divisor, BSError **error) {
if (divisor == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf ("Division by zero"));
return NULL;
} else if (divisor > ULONG_MAX) {
set_error (error, BS_ERROR_OVER, strdup_printf ("Divisor too big, must be less or equal to %lu", ULONG_MAX));
return NULL;
}
mpz_tdiv_q_ui (size->bytes, size->bytes, divisor);
return size;
}
/**
* bs_size_true_div:
*
* Divides @size1 by @size2.
*
* Returns: (transfer full): a string representing the floating-point number
* that equals to @size1 / @size2
*/
char* bs_size_true_div (const BSSize size1, const BSSize size2, BSError **error) {
mpf_t op1;
mpf_t op2;
char *ret = NULL;
if (mpz_cmp_ui (size2->bytes, 0) == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf("Division by zero"));
return 0;
}
mpf_init2 (op1, BS_FLOAT_PREC_BITS);
mpf_init2 (op2, BS_FLOAT_PREC_BITS);
mpf_set_z (op1, size1->bytes);
mpf_set_z (op2, size2->bytes);
mpf_div (op1, op1, op2);
gmp_asprintf (&ret, "%.*Fg", BS_FLOAT_PREC_BITS/3, op1);
mpf_clears (op1, op2, NULL);
return ret;
}
/**
* bs_size_true_div_int:
*
* Divides @size by @divisor.
*
* Note: Due to the limitations of the current implementation the maximum value
* @divisor is ULONG_MAX (which can differ from UINT64_MAX). An error
* (BS_ERROR_OVER) is returned if overflow happens.
*
* Returns: (transfer full): a string representing the floating-point number
* that equals to @size / @divisor
*/
char* bs_size_true_div_int (const BSSize size, uint64_t divisor, BSError **error) {
mpf_t op1;
char *ret = NULL;
if (divisor == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf ("Division by zero"));
return 0;
} else if (divisor > ULONG_MAX) {
set_error (error, BS_ERROR_OVER, strdup_printf ("Divisor too big, must be less or equal to %lu", ULONG_MAX));
return NULL;
}
mpf_init2 (op1, BS_FLOAT_PREC_BITS);
mpf_set_z (op1, size->bytes);
mpf_div_ui (op1, op1, divisor);
gmp_asprintf (&ret, "%.*Fg", BS_FLOAT_PREC_BITS/3, op1);
mpf_clear (op1);
return ret;
}
/**
* bs_size_mod:
*
* Gives @size1 modulo @size2 (i.e. the remainder of integer division @size1 /
* @size2). Gives the answer to the question "If I split @size1 into chunks of
* size @size2, what will be the remainder?"
* **This function ignores the signs of the sizes.**
*
* Returns: (transfer full): a #BSSize instance that is a remainder of
* @size1 / @size2 using integer division
*/
BSSize bs_size_mod (const BSSize size1, const BSSize size2, BSError **error) {
mpz_t aux;
BSSize ret = NULL;
if (mpz_cmp_ui (size2->bytes, 0) == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf ("Division by zero"));
return 0;
}
mpz_init (aux);
mpz_set (aux, size1->bytes);
if (mpz_sgn (size1->bytes) == -1)
/* negative @size1, get the absolute value so that we get results
matching the specification/documentation of this function */
mpz_neg (aux, aux);
ret = bs_size_new ();
mpz_mod (ret->bytes, aux, size2->bytes);
return ret;
}
/**
* bs_size_round_to_nearest:
* @round_to: to a multiple of what to round @size
* @dir: %BS_ROUND_DIR_UP to round up (to the nearest multiple of @round_to
* bigger than @size) or %BS_ROUND_DIR_DOWN to round down (to the
* nearest multiple of @round_to smaller than @size)
*
* Round @size to the nearest multiple of @round_to according to the direction
* given by @dir.
*
* Returns: (transfer full): a new instance of #BSSize that is @size rounded to
* a multiple of @round_to according to @dir
*/
BSSize bs_size_round_to_nearest (const BSSize size, const BSSize round_to, BSRoundDir dir, BSError **error) {
BSSize ret = NULL;
mpz_t q;
mpz_t aux_size;
if (mpz_cmp_ui (round_to->bytes, 0) == 0) {
set_error (error, BS_ERROR_ZERO_DIV, strdup_printf ("Division by zero"));
return NULL;
}
mpz_init (q);
if (dir == BS_ROUND_DIR_UP) {
mpz_cdiv_q (q, size->bytes, round_to->bytes);
} else if (dir == BS_ROUND_DIR_HALF_UP) {
/* round half up == add half of what to round to and round down */
mpz_init (aux_size);
mpz_fdiv_q_ui (aux_size, round_to->bytes, 2);
mpz_add (aux_size, aux_size, size->bytes);
mpz_fdiv_q (q, aux_size, round_to->bytes);
mpz_clear (aux_size);
} else
mpz_fdiv_q (q, size->bytes, round_to->bytes);
ret = bs_size_new ();
mpz_mul (ret->bytes, q, round_to->bytes);
mpz_clear (q);
return ret;
}
/***************
* COMPARISONS *
***************/
/**
* bs_size_cmp:
* @abs: whether to compare absolute values of @size1 and @size2 instead
*
* Compare @size1 and @size2. This function behaves like the standard *cmp*()
* functions.
*
* Returns: -1, 0, or 1 if @size1 is smaller, equal to or bigger than
* @size2 respectively comparing absolute values if @abs is %true
*/
int bs_size_cmp (const BSSize size1, const BSSize size2, bool abs) {
int ret = 0;
if (abs)
ret = mpz_cmpabs (size1->bytes, size2->bytes);
else
ret = mpz_cmp (size1->bytes, size2->bytes);
/* make sure we don't return things like 2 or -2 (which GMP can give us) */
if (ret > 0)
ret = 1;
else if (ret < 0)
ret = -1;
return ret;
}
/**
* bs_size_cmp_bytes:
* @abs: whether to compare absolute values of @size and @bytes instead.
*
* Compare @size and @bytes, i.e. the number of bytes @size has with
* @bytes. This function behaves like the standard *cmp*() functions.
*
* Returns: -1, 0, or 1 if @size is smaller, equal to or bigger than
* @bytes respectively comparing absolute values if @abs is %true
*/
int bs_size_cmp_bytes (const BSSize size, uint64_t bytes, bool abs) {
int ret = 0;
if (abs)
ret = mpz_cmpabs_ui (size->bytes, bytes);
else
ret = mpz_cmp_ui (size->bytes, bytes);
/* make sure we don't return things like 2 or -2 (which GMP can give us) */
if (ret > 0)
ret = 1;
else if (ret < 0)
ret = -1;
return ret;
}