• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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