1 /*
2 * Copyright (c) 2020-2024 Stefan Krah. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27
28 #include <cstdint>
29
30 #include <iostream>
31 #include <sstream>
32 #include <stdexcept>
33
34 #include "mpdecimal.h"
35 #include "decimal.hh"
36
37
38 /*****************************************************************************/
39 /* Library init */
40 /*****************************************************************************/
41
42 namespace {
43 class LibraryInit {
44 public:
LibraryInit()45 LibraryInit() { mpd_setminalloc(decimal::MINALLOC); }
46 };
47
48 const LibraryInit init;
49 } // namespace
50
51
52 /*****************************************************************************/
53 /* Context */
54 /*****************************************************************************/
55
56 namespace {
57
58 typedef struct {
59 const uint32_t flag;
60 const char *name;
61 const char *fqname;
62 void (* const raise)(const std::string& msg);
63 } cmap;
64
65 template<typename T>
66 void
raise(const std::string & msg)67 raise(const std::string& msg)
68 {
69 throw T(msg);
70 }
71
72 const cmap signal_map[] = {
73 { MPD_IEEE_Invalid_operation, "IEEEInvalidOperation", "decimal::IEEEInvalidOperation", raise<decimal::IEEEInvalidOperation> },
74 { MPD_Division_by_zero, "DivisionByZero", "decimal::DivisionByZero", raise<decimal::DivisionByZero> },
75 { MPD_Overflow, "Overflow", "decimal::Overflow", raise<decimal::Overflow> },
76 { MPD_Underflow, "Underflow", "decimal::Underflow", raise<decimal::Underflow> },
77 { MPD_Subnormal, "Subnormal", "decimal::Subnormal", raise<decimal::Subnormal> },
78 { MPD_Inexact, "Inexact", "decimal::Inexact", raise<decimal::Inexact> },
79 { MPD_Rounded, "Rounded", "decimal::Rounded", raise<decimal::Rounded> },
80 { MPD_Clamped, "Clamped", "decimal::Clamped", raise<decimal::Clamped> },
81 { UINT32_MAX, nullptr, nullptr, nullptr }
82 };
83
84 const cmap cond_map[] = {
85 { MPD_Invalid_operation, "InvalidOperation", "decimal::InvalidOperation", raise<decimal::InvalidOperation> },
86 { MPD_Conversion_syntax, "ConversionSyntax", "decimal::ConversionSyntax", raise<decimal::ConversionSyntax> },
87 { MPD_Division_impossible, "DivisionImpossible", "decimal::DivisionImpossible", raise<decimal::DivisionImpossible> },
88 { MPD_Division_undefined, "DivisionUndefined", "decimal::DivisionUndefined", raise<decimal::DivisionUndefined> },
89 { UINT32_MAX, nullptr, nullptr, nullptr }
90 };
91
92 std::string
signals(const uint32_t flags)93 signals(const uint32_t flags)
94 {
95 std::string s;
96 s.reserve(MPD_MAX_SIGNAL_LIST);
97
98 s += "[";
99 for (const cmap *c = signal_map; c->flag != UINT32_MAX; c++) {
100 if (flags & c->flag) {
101 if (s != "[") {
102 s += ", ";
103 }
104 s += c->name;
105 }
106 }
107
108 s += "]";
109
110 return s;
111 }
112
113 std::string
flags(const uint32_t flags)114 flags(const uint32_t flags)
115 {
116 std::string s;
117 s.reserve(MPD_MAX_FLAG_LIST);
118
119 s += "[";
120
121 for (const cmap *c = cond_map; c->flag != UINT32_MAX; c++) {
122 if (flags & c->flag) {
123 if (s != "[") {
124 s += ", ";
125 }
126 s += c->name;
127 }
128 }
129
130 for (const cmap *c = signal_map+1; c->flag != UINT32_MAX; c++) {
131 if (flags & c->flag) {
132 if (s != "[") {
133 s += ", ";
134 }
135 s += c->name;
136 }
137 }
138
139 s += "]";
140
141 return s;
142 }
143
144 /* Context for exact calculations with (practically) unbounded precision. */
145 const decimal::Context maxcontext {
146 MPD_MAX_PREC,
147 MPD_MAX_EMAX,
148 MPD_MIN_EMIN,
149 MPD_ROUND_HALF_EVEN,
150 MPD_IEEE_Invalid_operation,
151 0,
152 0
153 };
154
155 } // namespace
156
157
158 /*****************************************************************************/
159 /* Context API */
160 /*****************************************************************************/
161
162 namespace decimal {
163
164 /* Used for default initialization of new contexts such as TLS contexts. */
165 Context context_template {
166 16, /* prec */
167 999999, /* emax */
168 -999999, /* emin */
169 MPD_ROUND_HALF_EVEN, /* rounding */
170 MPD_IEEE_Invalid_operation|MPD_Division_by_zero|MPD_Overflow, /* traps */
171 0, /* clamp */
172 1 /* allcr */
173 };
174
175 #if defined(__OpenBSD__) || defined(__sun) || defined(_MSC_VER) && defined(_DLL)
getcontext()176 Context& getcontext() {
177 static thread_local Context _context{context_template};
178 return _context;
179 }
180 #else
181 thread_local Context context{context_template};
182 #endif
183
184 /* Factory function for creating a context for maximum unrounded arithmetic. */
185 Context
MaxContext()186 MaxContext()
187 {
188 return Context(maxcontext);
189 }
190
191 /* Factory function for creating IEEE interchange format contexts. */
192 Context
IEEEContext(int bits)193 IEEEContext(int bits)
194 {
195 mpd_context_t ctx;
196
197 if (mpd_ieee_context(&ctx, bits) < 0) {
198 throw ValueError("argument must be a multiple of 32, with a maximum of " +
199 std::to_string(MPD_IEEE_CONTEXT_MAX_BITS));
200 }
201
202 return Context(ctx);
203 }
204
205 void
raiseit(const uint32_t status)206 Context::raiseit(const uint32_t status)
207 {
208 if (status & MPD_Malloc_error) {
209 throw MallocError("out of memory");
210 }
211
212 const std::string msg = flags(status);
213 for (const cmap *c = cond_map; c->flag != UINT32_MAX; c++) {
214 if (status & c->flag) {
215 c->raise(msg);
216 }
217 }
218
219 for (const cmap *c = signal_map+1; c->flag != UINT32_MAX; c++) {
220 if (status & c->flag) {
221 c->raise(msg);
222 }
223 }
224
225 throw RuntimeError("internal_error: unknown status flag");
226 }
227
228 std::string
repr() const229 Context::repr() const
230 {
231 const int rounding = round();
232 std::ostringstream ss;
233
234 if (rounding < 0 || rounding >= MPD_ROUND_GUARD) {
235 throw RuntimeError("internal_error: invalid rounding mode");
236 }
237
238 const char *round_str = mpd_round_string[rounding];
239
240 ss << "Context(prec=" << prec() << ", " <<
241 "emax=" << emax() << ", " <<
242 "emin=" << emin() << ", " <<
243 "round=" << round_str << ", " <<
244 "clamp=" << clamp() << ", " <<
245 "traps=" << signals(traps()) << ", " <<
246 "status=" << signals(status()) <<
247 ")";
248
249 return ss.str();
250 }
251
252 std::ostream&
operator <<(std::ostream & os,const Context & c)253 operator<<(std::ostream& os, const Context& c)
254 {
255 os << c.repr();
256 return os;
257 }
258
259
260 /*****************************************************************************/
261 /* Decimal API */
262 /*****************************************************************************/
263
264 Decimal
exact(const char * const s,Context & c)265 Decimal::exact(const char *const s, Context& c)
266 {
267 Decimal result;
268 uint32_t status = 0;
269
270 if (s == nullptr) {
271 throw ValueError("Decimal::exact: string argument is NULL");
272 }
273
274 mpd_qset_string_exact(result.get(), s, &status);
275 c.raise(status);
276 return result;
277 }
278
279 Decimal
exact(const std::string & s,Context & c)280 Decimal::exact(const std::string& s, Context& c)
281 {
282 return Decimal::exact(s.c_str(), c);
283 }
284
285 Decimal
ln10(int64_t n,Context & c)286 Decimal::ln10(int64_t n, Context& c)
287 {
288 Decimal result;
289 uint32_t status = 0;
290
291 if (n < 1 || n > MPD_MAX_PREC) {
292 throw ValueError("Decimal::ln10: prec argument must in [1, MAX_PREC]");
293 }
294
295 mpd_ssize_t nn = util::safe_downcast<mpd_ssize_t, int64_t>(n);
296 mpd_qln10(result.get(), nn, &status);
297
298 c.raise(status);
299 return result;
300 }
301
302 int32_t
radix()303 Decimal::radix()
304 {
305 return 10;
306 }
307
308 std::string
repr(bool capitals) const309 Decimal::repr(bool capitals) const
310 {
311 std::string s = to_sci(capitals);
312
313 return "Decimal(\"" + s + "\")";
314 }
315
316 std::ostream&
operator <<(std::ostream & os,const Decimal & dec)317 operator<<(std::ostream& os, const Decimal& dec)
318 {
319 os << dec.to_sci();
320
321 return os;
322 }
323
324 } // namespace decimal
325