-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathdecimal.c
298 lines (274 loc) · 9.64 KB
/
decimal.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "balrt.h"
#include "third-party/decNumber/decQuad.h"
#include "third-party/dtoa/emyg_dtoa.h"
typedef GC decQuad *DecimalPtr;
// Status flags that we should treat as failure
// XXX need to investigate DEC_Underflow
#define DECIMAL_STATUS_FAIL DEC_Errors
static TaggedPtrPanicCode finish(decQuad *dq, decContext *cx);
static bool decimalListContains(const DecimalConstPtr *start, const DecimalConstPtr *end, const decQuad *dq);
static inline TaggedPtr createDecimal(decQuad *dq) {
DecimalPtr dp = _bal_alloc(sizeof(decQuad));
*dp = *dq;
return ptrAddFlags(dp, (uint64_t)TAG_DECIMAL << TAG_SHIFT);
}
static inline const decQuad *taggedToDecQuad(TaggedPtr tp) {
UntypedPtr p = taggedToPtr(tp);
return (void *)p;
}
static inline void initContext(decContext *cx) {
cx->status = 0;
cx->round = DEC_ROUND_HALF_EVEN;
}
TaggedPtrPanicCode _bal_decimal_add(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d;
decContext cx;
initContext(&cx);
decQuadAdd(&d, taggedToDecQuad(tp1), taggedToDecQuad(tp2), &cx);
return finish(&d, &cx);
}
TaggedPtrPanicCode _bal_decimal_sub(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d;
decContext cx;
initContext(&cx);
decQuadSubtract(&d, taggedToDecQuad(tp1), taggedToDecQuad(tp2), &cx);
return finish(&d, &cx);
}
TaggedPtrPanicCode _bal_decimal_mul(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d;
decContext cx;
initContext(&cx);
decQuadMultiply(&d, taggedToDecQuad(tp1), taggedToDecQuad(tp2), &cx);
return finish(&d, &cx);
}
TaggedPtrPanicCode _bal_decimal_div(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d;
decContext cx;
initContext(&cx);
decQuadDivide(&d, taggedToDecQuad(tp1), taggedToDecQuad(tp2), &cx);
return finish(&d, &cx);
}
TaggedPtrPanicCode _bal_decimal_rem(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d;
decContext cx;
initContext(&cx);
decQuadRemainder(&d, taggedToDecQuad(tp1), taggedToDecQuad(tp2), &cx);
return finish(&d, &cx);
}
static TaggedPtrPanicCode finish(decQuad *dq, decContext *cx) {
TaggedPtrPanicCode result;
enum decClass class = decQuadClass(dq);
if (class == DEC_CLASS_POS_ZERO ||
class == DEC_CLASS_NEG_ZERO ||
class == DEC_CLASS_POS_SUBNORMAL ||
class == DEC_CLASS_NEG_SUBNORMAL) {
decQuadZero(dq);
result.panicCode = 0;
result.ptr = createDecimal(dq);
return result;
}
uint32_t status = cx->status;
if (status & DECIMAL_STATUS_FAIL) {
if (status & DEC_Division_by_zero) {
result.panicCode = PANIC_DIVIDE_BY_ZERO;
}
else if (status & DEC_Overflow) {
result.panicCode = PANIC_ARITHMETIC_OVERFLOW;
}
else if (status & DEC_Underflow) {
// The reason to do this check is, there are subnormal cases
// which cannot be identified using decQuadClass.
// Consider this example: mul(9.999999999999999999999999999999999E-6001, 1E-143)
// Before rounding the result is 9.999999999999999999999999999999999E-6144,
// which is subnormal. After rounding The class of result is
// +Normal and value is 1.000000000000000000000000000000000E-6143
// Also underflow flag is raised.
// But according the The X3.274 subset, the expected value should be 0.
// Therefore it is unable to identify cases like this using decQuadClass.
// In the library,
// 1. Multiplication happens in two logical stages:
// perform the multiplication to a precision greater than allowed by decimal128 format,
// and then round to fit into a decimal128
// 2. It is possible for the result of the first stage to be subnormal,
// but then be rounded in the second stage to become normal
// 3. In this case, decNumber in subset mode will return 0.
// 4. We can detect this case in decQuad by checking the Underflow flag.
decQuadZero(dq);
result.panicCode = 0;
result.ptr = createDecimal(dq);
}
else {
// status & DEC_IEEE_754_Invalid_operation = true
// This is used in a case where a float operation would return NaN
result.panicCode = PANIC_INVALID_DECIMAL;
}
}
else {
result.panicCode = 0;
result.ptr = createDecimal(dq);
}
return result;
}
TaggedPtr _bal_decimal_neg(TaggedPtr tp) {
const decQuad *dq = taggedToDecQuad(tp);
if (decQuadClass(dq) == DEC_CLASS_POS_ZERO) {
return tp;
}
decQuad d;
decQuadCopyNegate(&d, dq);
return createDecimal(&d);
}
bool _bal_decimal_exact_eq(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d1;
decQuad d2;
decQuadCanonical(&d1, taggedToDecQuad(tp1));
decQuadCanonical(&d2, taggedToDecQuad(tp2));
return memcmp(&d1, &d2, DECQUAD_Bytes) == 0;
}
int64_t _bal_decimal_cmp(TaggedPtr tp1, TaggedPtr tp2) {
decQuad d;
decContext cx;
initContext(&cx);
decQuadCompare(&d, taggedToDecQuad(tp1), taggedToDecQuad(tp2), &cx);
// The return value of decQuadCompare can be 1, 0, -1 or NaN:
// since this is represented as a decQuad, decQuadClass is used to
// identify 1, 0, -1.
// NaN is returned only if lhs or rhs is a NaN.
// It is not an expected output because both arguments are valid decimals.
enum decClass class = decQuadClass(&d);
if (class == DEC_CLASS_POS_ZERO) {
return 0;
}
else if (class == DEC_CLASS_POS_NORMAL) {
return 1;
}
else {
return -1;
}
}
double _bal_decimal_to_float(TaggedPtr tp) {
char dblStr[DECQUAD_String];
decQuadToString(taggedToDecQuad(tp), dblStr);
return strtod(dblStr, NULL);
}
TaggedPtr _bal_decimal_from_int(int64_t val) {
decQuad d;
if (INT32_MIN <= val && val <= INT32_MAX) {
decQuadFromInt32(&d, (int32_t)val);
}
else {
#define STR_CONVERT(x) #x
#define STR(x) STR_CONVERT(x)
#define INT64_MAX_LEN sizeof(STR(INT64_MIN))
char intStr[INT64_MAX_LEN];
sprintf(intStr, "%" PRId64, val);
decContext cx;
initContext(&cx);
decQuadFromString(&d, intStr, &cx);
}
return createDecimal(&d);
}
TaggedPtrPanicCode _bal_decimal_from_float(double val) {
TaggedPtrPanicCode result;
if (isnan(val)) {
result.panicCode = PANIC_INVALID_DECIMAL;
return result;
}
if (isinf(val)) {
result.panicCode = PANIC_ARITHMETIC_OVERFLOW;
return result;
}
result.panicCode = 0;
decQuad d;
if (val == 0.0) {
decQuadZero(&d);
result.ptr = createDecimal(&d);
return result;
}
char str[EMYG_DTOA_BUFFER_LEN];
emyg_dtoa_non_special(val, str);
decContext cx;
initContext(&cx);
decQuadFromString(&d, str, &cx);
decQuad dTrim;
decQuadReduce(&dTrim, &d, &cx);
result.ptr = createDecimal(&dTrim);
return result;
}
IntWithOverflow _bal_decimal_to_int(TaggedPtr tp) {
decQuad dQuantize;
decQuad dZero;
decQuadZero(&dZero);
decContext cx;
initContext(&cx);
decQuadQuantize(&dQuantize, taggedToDecQuad(tp), &dZero, &cx);
IntWithOverflow res;
if (cx.status & DEC_Invalid_operation) {
// The invalid operation flag is raised,
// when maximum precision(34 digits) is not enough to represent quantized decimal value.
// This situation can be considered as an overflow scenario,
// because reaching maximum precision of decimal is an overflow of 64 bit integer(19 digits).
res.overflow = true;
return res;
}
char str[DECQUAD_String];
decQuadToString(&dQuantize, str);
errno = 0;
int64_t value = strtol(str, NULL, 0);
if (errno == ERANGE) {
res.overflow = true;
return res;
}
res.overflow = false;
res.value = value;
return res;
}
bool _bal_decimal_subtype_contains(UniformSubtypePtr stp, TaggedPtr tp) {
DecimalSubtypePtr dstp = (DecimalSubtypePtr)stp;
bool included = dstp->included;
return decimalListContains(dstp->decimals, dstp->decimals + dstp->nDecimals, taggedToDecQuad(tp)) == included;
}
// Do binary search for tp
// Approximately the same code as tidListContains
static bool decimalListContains(const DecimalConstPtr *start, const DecimalConstPtr *end, const decQuad *dq) {
// Lower bound inclusive; upper bound is exclusive
// Invariant: if there is a member in the list == to dq, then its address p
// satisfies start <= p < end
decContext cx;
initContext(&cx);
while (start < end) {
const DecimalConstPtr *mid = start + (end - start)/2;
decQuad cmp;
decQuadCompare(&cmp, dq, (const decQuad *) *mid, &cx);
enum decClass cmpClass = decQuadClass(&cmp);
// We have start <= mid < end
// int64_t cmp = _bal_decimal_cmp(tp, *mid);
if (cmpClass == DEC_CLASS_POS_ZERO) {
return true;
}
if (cmpClass == DEC_CLASS_NEG_NORMAL) {
// this decreases end, since mid < end
// still have start <= end
end = mid;
}
else {
// this increases start, since mid >= start
// still have start <= end
start = mid + 1;
}
}
// start == end, so there is no such member
return false;
}
typedef struct DecimalFillerDesc {
TaggedPtr (*create)(struct DecimalFillerDesc *fillerDesc, bool *hasIdentityPtr);
decQuad *val;
} *DecimalFillerDescPtr;
TaggedPtr _bal_decimal_filler_create(DecimalFillerDescPtr fillerDesc, bool *hasIdentityPtr) {
*hasIdentityPtr = false;
return createDecimal(fillerDesc->val);
}