Branch data Line data Source code
1 : : // Copyright 2019-2026 David Robillard <d@drobilla.net>
2 : : // SPDX-License-Identifier: ISC
3 : :
4 : : #include "big_decimal.h"
5 : : #include "number_utils.h"
6 : : #include "parse_decimal.h"
7 : : #include "read_utils.h"
8 : : #include "result.h"
9 : : #include "write_utils.h"
10 : :
11 : : #include <exess/exess.h>
12 : :
13 : : #include <assert.h>
14 : : #include <stddef.h>
15 : :
16 : : typedef enum {
17 : : EXESS_POINT_AFTER, ///< Decimal point is after all significant digits
18 : : EXESS_POINT_BEFORE, ///< Decimal point is before all significant digits
19 : : EXESS_POINT_BETWEEN, ///< Decimal point is between significant digits
20 : : } ExessPointLocation;
21 : :
22 : : typedef struct {
23 : : ExessPointLocation point_loc; ///< Location of decimal point
24 : : unsigned n_zeros_before; ///< Number of extra zeros before point
25 : : unsigned n_zeros_after; ///< Number of extra zeros after point
26 : : } DecimalMetrics;
27 : :
28 : : static DecimalMetrics
29 : 4773 : decimal_metrics(const BigDecimal count)
30 : : {
31 : 4773 : DecimalMetrics metrics = {EXESS_POINT_AFTER, 0U, 0U};
32 : :
33 [ + + ]: 4773 : if (count.expt >= (int)count.n_digits) {
34 : 2441 : metrics.point_loc = EXESS_POINT_AFTER;
35 : 2441 : metrics.n_zeros_before = (unsigned)count.expt - count.n_digits;
36 : 2441 : metrics.n_zeros_after = 1U;
37 [ + + ]: 2332 : } else if (count.expt < 0) {
38 : 2247 : metrics.point_loc = EXESS_POINT_BEFORE;
39 : 2247 : metrics.n_zeros_before = 1U;
40 : 2247 : metrics.n_zeros_after = (unsigned)(-count.expt);
41 : : } else {
42 : 85 : metrics.point_loc = EXESS_POINT_BETWEEN;
43 : : }
44 : :
45 : 4773 : return metrics;
46 : : }
47 : :
48 : : EXESS_NONBLOCKING ExessVariableResult
49 : 4311 : exess_read_decimal(const char* const str,
50 : : const size_t out_size,
51 : : void* const out)
52 : : {
53 [ + + ]: 4311 : if (out_size < sizeof(BigDecimal) + 2U) {
54 : 5 : return VRESULT(EXESS_NO_SPACE, 0U, sizeof(BigDecimal) + 2U);
55 : : }
56 : :
57 : 4306 : char* const digits = (char*)out + sizeof(BigDecimal);
58 : 4306 : const size_t digits_size = out_size - sizeof(BigDecimal) - 1U;
59 : :
60 : 4306 : const size_t i = skip_whitespace(str);
61 : 4306 : BigDecimal d = {EXESS_NAN, 0U, 0};
62 : 4306 : const ExessResult r = parse_decimal(str + i, &d, digits_size, digits);
63 : :
64 : : // Trim trailing zeros
65 [ + + ]: 4306 : if (r.status <= EXESS_LOSS) {
66 [ + + + + ]: 5740 : while (d.n_digits > 1 && digits[d.n_digits - 1] == '0') {
67 : 1442 : --d.n_digits;
68 : : }
69 : : }
70 : :
71 : 4306 : *(BigDecimal*)out = d; // Set output header
72 : 4306 : digits[d.n_digits] = '\0'; // Terminate digits for safety/convenience
73 : :
74 : 4306 : return VRESULT(r.status, i + r.count, sizeof(d) + d.n_digits + 1U);
75 : : }
76 : :
77 : : EXESS_NONBLOCKING ExessResult
78 : 4795 : exess_write_decimal(const size_t value_size,
79 : : const void* const value,
80 : : const size_t buf_size,
81 : : char* const buf)
82 : : {
83 : 4795 : const BigDecimal decimal = *(const BigDecimal*)value;
84 : 4795 : const char* const digits = (const char*)value + sizeof(decimal);
85 : :
86 [ + + ]: 4795 : if (decimal.kind < EXESS_NEGATIVE_ZERO ||
87 [ + + ]: 4789 : value_size < sizeof(decimal) + decimal.n_digits) {
88 : 7 : return end_write(EXESS_BAD_VALUE, buf_size, buf, 0);
89 : : }
90 : :
91 [ + + ]: 4788 : if (decimal.kind == EXESS_NEGATIVE_ZERO) {
92 : 8 : return write_special(4, "-0.0", buf_size, buf);
93 : : }
94 : :
95 [ + + ]: 4780 : if (decimal.kind == EXESS_POSITIVE_ZERO) {
96 : 7 : return write_special(3, "0.0", buf_size, buf);
97 : : }
98 : :
99 : 4773 : size_t i = 0;
100 [ + + ]: 4773 : if (decimal.kind == EXESS_NEGATIVE) {
101 : 12 : i += write_char('-', buf_size, buf, i);
102 : : }
103 : :
104 : 4773 : const DecimalMetrics metrics = decimal_metrics(decimal);
105 : 4773 : const unsigned n_zeros = metrics.n_zeros_before + metrics.n_zeros_after;
106 : 4773 : const unsigned n_total = decimal.n_digits + 1U + n_zeros;
107 [ + + ]: 4773 : if (i + n_total >= buf_size) {
108 : 660 : return end_write(EXESS_NO_SPACE, buf_size, buf, i + n_total);
109 : : }
110 : :
111 [ + + ]: 4113 : if (metrics.point_loc == EXESS_POINT_AFTER) {
112 : 2128 : i += write_string(decimal.n_digits, digits, buf_size, buf, i);
113 : 2128 : i += write_zeros(metrics.n_zeros_before, buf_size, buf, i);
114 : 2128 : i += write_string(2, ".0", buf_size, buf, i);
115 [ + + ]: 1985 : } else if (metrics.point_loc == EXESS_POINT_BEFORE) {
116 : 1919 : i += write_string(2, "0.", buf_size, buf, i);
117 : 1919 : i += write_zeros(metrics.n_zeros_after, buf_size, buf, i);
118 : 1919 : i += write_string(decimal.n_digits, digits, buf_size, buf, i);
119 : : } else {
120 [ - + ]: 66 : assert(metrics.point_loc == EXESS_POINT_BETWEEN);
121 [ - + ]: 66 : assert(decimal.expt >= 0);
122 : 66 : const size_t n_before = (size_t)decimal.expt;
123 : 66 : const size_t n_after = decimal.n_digits - n_before;
124 : 66 : i += write_string(n_before, digits, buf_size, buf, i);
125 : 66 : i += write_char('.', buf_size, buf, i);
126 : 66 : i += write_string(n_after, digits + n_before, buf_size, buf, i);
127 : : }
128 : :
129 : 4113 : return end_write(EXESS_SUCCESS, buf_size, buf, i);
130 : : }
|