diff options
Diffstat (limited to 'src/decimal.c')
-rw-r--r-- | src/decimal.c | 64 |
1 files changed, 63 insertions, 1 deletions
diff --git a/src/decimal.c b/src/decimal.c index 7f78c8a7..6c0bad59 100644 --- a/src/decimal.c +++ b/src/decimal.c @@ -1,5 +1,5 @@ /* - Copyright 2019 David Robillard <http://drobilla.net> + Copyright 2019-2020 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -18,8 +18,70 @@ #include "int_math.h" +#include <assert.h> +#include <float.h> +#include <math.h> + int serd_count_digits(const uint64_t i) { return i == 0 ? 1 : (int)serd_ilog10(i) + 1; } + +unsigned +serd_double_int_digits(const double abs) +{ + const double lg = ceil(log10(floor(abs) + 1.0)); + return lg < 1.0 ? 1U : (unsigned)lg; +} + +unsigned +serd_decimals(const double d, char* const buf, const unsigned frac_digits) +{ + assert(isfinite(d)); + + // Point s to decimal point location + const double abs_d = fabs(d); + const double int_part = floor(abs_d); + const unsigned int_digits = serd_double_int_digits(abs_d); + unsigned n_bytes = 0; + char* s = buf + int_digits; + if (d < 0.0) { + *buf = '-'; + ++s; + } + + // Write integer part (right to left) + char* t = s - 1; + uint64_t dec = (uint64_t)int_part; + do { + *t-- = (char)('0' + dec % 10); + } while ((dec /= 10) > 0); + + + *s++ = '.'; + + // Write fractional part (right to left) + double frac_part = fabs(d - int_part); + if (frac_part < DBL_EPSILON) { + *s++ = '0'; + n_bytes = (unsigned)(s - buf); + } else { + uint64_t frac = (uint64_t)llround(frac_part * pow(10.0, (int)frac_digits)); + s += frac_digits - 1; + unsigned i = 0; + + // Skip trailing zeros + for (; i < frac_digits - 1 && !(frac % 10); ++i, --s, frac /= 10) {} + + n_bytes = (unsigned)(s - buf) + 1u; + + // Write digits from last trailing zero to decimal point + for (; i < frac_digits; ++i) { + *s-- = (char)('0' + (frac % 10)); + frac /= 10; + } + } + + return n_bytes; +} |