aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/exess/src/decimal.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/exess/src/decimal.c')
-rw-r--r--subprojects/exess/src/decimal.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/subprojects/exess/src/decimal.c b/subprojects/exess/src/decimal.c
new file mode 100644
index 00000000..a8ce8ad5
--- /dev/null
+++ b/subprojects/exess/src/decimal.c
@@ -0,0 +1,267 @@
+/*
+ Copyright 2019-2021 David Robillard <d@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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "decimal.h"
+#include "digits.h"
+#include "read_utils.h"
+#include "string_utils.h"
+#include "strtod.h"
+#include "warnings.h"
+#include "write_utils.h"
+
+#include "exess/exess.h"
+
+#include <math.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <assert.h>
+
+typedef enum {
+ EXESS_POINT_AFTER, ///< Decimal point is after all significant digits
+ EXESS_POINT_BEFORE, ///< Decimal point is before all significant digits
+ EXESS_POINT_BETWEEN, ///< Decimal point is between significant digits
+} ExessPointLocation;
+
+typedef struct {
+ ExessPointLocation point_loc; ///< Location of decimal point
+ unsigned n_zeros_before; ///< Number of extra zeros before point
+ unsigned n_zeros_after; ///< Number of extra zeros after point
+} DecimalMetrics;
+
+static DecimalMetrics
+decimal_metrics(const ExessDecimalDouble count)
+{
+ const int expt =
+ count.expt >= 0 ? (count.expt - (int)count.n_digits + 1) : count.expt;
+
+ DecimalMetrics metrics = {EXESS_POINT_AFTER, 0u, 0u};
+
+ if (count.expt >= (int)count.n_digits - 1) {
+ metrics.point_loc = EXESS_POINT_AFTER;
+ metrics.n_zeros_before = (unsigned)count.expt - (count.n_digits - 1u);
+ metrics.n_zeros_after = 1u;
+ } else if (count.expt < 0) {
+ metrics.point_loc = EXESS_POINT_BEFORE;
+ metrics.n_zeros_before = 1u;
+ metrics.n_zeros_after = (unsigned)(-expt - 1);
+ } else {
+ metrics.point_loc = EXESS_POINT_BETWEEN;
+ }
+
+ return metrics;
+}
+
+static ExessNumberKind
+number_kind(const double d)
+{
+ EXESS_DISABLE_CONVERSION_WARNINGS
+ const int fpclass = fpclassify(d);
+ const bool is_negative = signbit(d);
+ EXESS_RESTORE_WARNINGS
+
+ switch (fpclass) {
+ case FP_ZERO:
+ return is_negative ? EXESS_NEGATIVE_ZERO : EXESS_POSITIVE_ZERO;
+ case FP_INFINITE:
+ return is_negative ? EXESS_NEGATIVE_INFINITY : EXESS_POSITIVE_INFINITY;
+ case FP_NORMAL:
+ case FP_SUBNORMAL:
+ return is_negative ? EXESS_NEGATIVE : EXESS_POSITIVE;
+ default:
+ break;
+ }
+
+ return EXESS_NAN;
+}
+
+ExessDecimalDouble
+exess_measure_decimal(const double d, const unsigned max_precision)
+{
+ ExessDecimalDouble value = {number_kind(d), 0, 0, {0}};
+
+ if (value.kind != EXESS_NEGATIVE && value.kind != EXESS_POSITIVE) {
+ return value;
+ }
+
+ // Get decimal digits
+ const double abs_d = fabs(d);
+ const ExessDigitCount count =
+ exess_digits(abs_d, value.digits, max_precision);
+
+ assert(count.count == 1 || value.digits[count.count - 1] != '0');
+
+ value.n_digits = count.count;
+ value.expt = count.expt;
+
+ return value;
+}
+
+ExessDecimalDouble
+exess_measure_float(const float f)
+{
+ return exess_measure_decimal((double)f, FLT_DECIMAL_DIG);
+}
+
+ExessDecimalDouble
+exess_measure_double(const double d)
+{
+ return exess_measure_decimal(d, DBL_DECIMAL_DIG);
+}
+
+static size_t
+exess_decimal_double_string_length(const ExessDecimalDouble decimal)
+{
+ switch (decimal.kind) {
+ case EXESS_NEGATIVE:
+ break;
+ case EXESS_NEGATIVE_INFINITY:
+ return 0;
+ case EXESS_NEGATIVE_ZERO:
+ return 4;
+ case EXESS_POSITIVE_ZERO:
+ return 3;
+ case EXESS_POSITIVE:
+ break;
+ case EXESS_POSITIVE_INFINITY:
+ case EXESS_NAN:
+ return 0;
+ }
+
+ const DecimalMetrics metrics = decimal_metrics(decimal);
+ const unsigned n_zeros = metrics.n_zeros_before + metrics.n_zeros_after;
+ const bool is_negative = decimal.kind == EXESS_NEGATIVE;
+
+ return is_negative + decimal.n_digits + 1 + n_zeros;
+}
+
+static size_t
+copy_digits(char* const dest, const char* const src, const size_t n)
+{
+ memcpy(dest, src, n);
+ return n;
+}
+
+static size_t
+set_zeros(char* const dest, const size_t n)
+{
+ memset(dest, '0', n);
+ return n;
+}
+
+static ExessResult
+read_decimal_number(double* const out, const char* const str)
+{
+ *out = (double)NAN;
+
+ if (str[0] == '+' || str[0] == '-') {
+ if (str[1] != '.' && !is_digit(str[1])) {
+ return result(EXESS_EXPECTED_DIGIT, 1);
+ }
+ } else if (str[0] != '.' && !is_digit(str[0])) {
+ return result(EXESS_EXPECTED_DIGIT, 0);
+ }
+
+ const size_t i = skip_whitespace(str);
+ ExessDecimalDouble in = {EXESS_NAN, 0u, 0, {0}};
+ const ExessResult r = parse_decimal(&in, str + i);
+
+ if (!r.status) {
+ *out = parsed_double_to_double(in);
+ }
+
+ return result(r.status, i + r.count);
+}
+
+ExessResult
+exess_read_decimal(double* const out, const char* const str)
+{
+ const size_t i = skip_whitespace(str);
+ const ExessResult r = read_decimal_number(out, str + i);
+
+ return end_read(r.status, str, i + r.count);
+}
+
+ExessResult
+exess_write_decimal_double(const ExessDecimalDouble decimal,
+ const size_t buf_size,
+ char* const buf)
+{
+ if (!buf) {
+ return result(EXESS_SUCCESS, exess_decimal_double_string_length(decimal));
+ }
+
+ size_t i = 0;
+ if (buf_size < 3) {
+ return end_write(EXESS_NO_SPACE, buf_size, buf, 0);
+ }
+
+ switch (decimal.kind) {
+ case EXESS_NEGATIVE:
+ buf[i++] = '-';
+ break;
+ case EXESS_NEGATIVE_INFINITY:
+ return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
+ case EXESS_NEGATIVE_ZERO:
+ return write_special(4, "-0.0", buf_size, buf);
+ case EXESS_POSITIVE_ZERO:
+ return write_special(3, "0.0", buf_size, buf);
+ case EXESS_POSITIVE:
+ break;
+ case EXESS_POSITIVE_INFINITY:
+ case EXESS_NAN:
+ return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
+ }
+
+ const DecimalMetrics metrics = decimal_metrics(decimal);
+ const unsigned n_zeros = metrics.n_zeros_before + metrics.n_zeros_after;
+ if (buf_size - i <= decimal.n_digits + 1 + n_zeros) {
+ return end_write(EXESS_NO_SPACE, buf_size, buf, 0);
+ }
+
+ if (metrics.point_loc == EXESS_POINT_AFTER) {
+ i += copy_digits(buf + i, decimal.digits, decimal.n_digits);
+ i += set_zeros(buf + i, metrics.n_zeros_before);
+ buf[i++] = '.';
+ buf[i++] = '0';
+ } else if (metrics.point_loc == EXESS_POINT_BEFORE) {
+ buf[i++] = '0';
+ buf[i++] = '.';
+ i += set_zeros(buf + i, metrics.n_zeros_after);
+ i += copy_digits(buf + i, decimal.digits, decimal.n_digits);
+ } else {
+ assert(metrics.point_loc == EXESS_POINT_BETWEEN);
+ assert(decimal.expt >= -1);
+
+ const size_t n_before = (size_t)decimal.expt + 1u;
+ const size_t n_after = decimal.n_digits - n_before;
+
+ i += copy_digits(buf + i, decimal.digits, n_before);
+ buf[i++] = '.';
+ memcpy(buf + i, decimal.digits + n_before, n_after);
+ i += n_after;
+ }
+
+ return end_write(EXESS_SUCCESS, buf_size, buf, i);
+}
+
+ExessResult
+exess_write_decimal(const double value, const size_t n, char* const buf)
+{
+ const ExessDecimalDouble decimal = exess_measure_double(value);
+
+ return exess_write_decimal_double(decimal, n, buf);
+}