Blame src/bs_size.c

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