forked from php/php-src
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathround.c
More file actions
226 lines (196 loc) · 6.28 KB
/
Copy pathround.c
File metadata and controls
226 lines (196 loc) · 6.28 KB
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
/*
+----------------------------------------------------------------------+
| Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
| This source file is subject to the Modified BSD License that is |
| bundled with this package in the file LICENSE, and is available |
| through the World Wide Web at <https://www.php.net/license/>. |
| |
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Saki Takamachi <saki@php.net> |
+----------------------------------------------------------------------+
*/
#include "bcmath.h"
#include "private.h"
#include <stddef.h>
/* Returns the scale of the value after rounding. */
size_t bc_round(bc_num num, zend_long precision, zend_enum_RoundingMode mode, bc_num *result)
{
/* clear result */
bc_free_num(result);
/*
* The following cases result in an early return:
*
* - When rounding to an integer part which is larger than the number
* e.g. Rounding 21.123 to 3 digits before the decimal point.
* - When rounding to a greater decimal precision then the number has, the number is unchanged
* e.g. Rounding 21.123 to 4 digits after the decimal point.
* - If the fractional part ends with zeros, the zeros are omitted and the number of digits in num is reduced.
* Meaning we might end up in the previous case.
*/
/* e.g. value is 0.1 and precision is -3, ret is 0 or 1000 */
if (precision < 0 && num->n_len < (size_t) (-(precision + Z_L(1))) + 1) {
switch (mode) {
case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
case ZEND_ENUM_RoundingMode_HalfTowardsZero:
case ZEND_ENUM_RoundingMode_HalfEven:
case ZEND_ENUM_RoundingMode_HalfOdd:
case ZEND_ENUM_RoundingMode_TowardsZero:
*result = bc_copy_num(BCG(_zero_));
return 0;
case ZEND_ENUM_RoundingMode_PositiveInfinity:
if (num->n_sign == MINUS) {
*result = bc_copy_num(BCG(_zero_));
return 0;
}
break;
case ZEND_ENUM_RoundingMode_NegativeInfinity:
if (num->n_sign == PLUS) {
*result = bc_copy_num(BCG(_zero_));
return 0;
}
break;
case ZEND_ENUM_RoundingMode_AwayFromZero:
break;
}
if (bc_is_zero(num)) {
*result = bc_copy_num(BCG(_zero_));
return 0;
}
/* If precision is -3, it becomes 1000. Negate in unsigned space so
* precision == ZEND_LONG_MIN doesn't overflow signed long. */
zend_ulong magnitude = -(zend_ulong) precision;
*result = bc_new_num((size_t) magnitude + 1, 0);
(*result)->n_value[0] = 1;
(*result)->n_sign = num->n_sign;
return 0;
}
/* Just like bcadd('1', '1', 4) becomes '2.0000', it pads with zeros at the end if necessary. */
if (precision >= 0 && num->n_scale <= precision) {
if (num->n_scale == precision) {
*result = bc_copy_num(num);
} else if(num->n_scale < precision) {
*result = bc_new_num(num->n_len, precision);
(*result)->n_sign = num->n_sign;
memcpy((*result)->n_value, num->n_value, num->n_len + num->n_scale);
}
return precision;
}
/*
* If the calculation result is a negative value, there is an early return,
* so no underflow will occur.
*/
size_t rounded_len = num->n_len + precision;
/*
* Initialize result
* For example, if rounded_len is 0, it means trying to round 50 to 100 or 0.
* If the result of rounding is carried over, it will be added later, so first set it to 0 here.
*/
if (rounded_len == 0) {
*result = bc_new_num(1, 0);
} else {
*result = bc_new_num(num->n_len, precision > 0 ? precision : 0);
memcpy((*result)->n_value, num->n_value, rounded_len);
}
(*result)->n_sign = num->n_sign;
const char *nptr = num->n_value + rounded_len;
/* Check cases that can be determined without looping. */
switch (mode) {
case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
if (*nptr >= 5) {
goto up;
} else if (*nptr < 5) {
goto check_zero;
}
break;
case ZEND_ENUM_RoundingMode_HalfTowardsZero:
case ZEND_ENUM_RoundingMode_HalfEven:
case ZEND_ENUM_RoundingMode_HalfOdd:
if (*nptr > 5) {
goto up;
} else if (*nptr < 5) {
goto check_zero;
}
/* if *nptr == 5, we need to look-up further digits before making a decision. */
break;
case ZEND_ENUM_RoundingMode_PositiveInfinity:
if (num->n_sign != PLUS) {
goto check_zero;
} else if (*nptr > 0) {
goto up;
}
/* if *nptr == 0, a loop is required for judgment. */
break;
case ZEND_ENUM_RoundingMode_NegativeInfinity:
if (num->n_sign != MINUS) {
goto check_zero;
} else if (*nptr > 0) {
goto up;
}
/* if *nptr == 0, a loop is required for judgment. */
break;
case ZEND_ENUM_RoundingMode_TowardsZero:
goto check_zero;
case ZEND_ENUM_RoundingMode_AwayFromZero:
if (*nptr > 0) {
goto up;
}
/* if *nptr == 0, a loop is required for judgment. */
break;
}
/* Loop through the remaining digits. */
size_t count = num->n_len + num->n_scale - rounded_len - 1;
nptr++;
while ((count > 0) && (*nptr == 0)) {
count--;
nptr++;
}
if (count > 0) {
goto up;
}
switch (mode) {
case ZEND_ENUM_RoundingMode_HalfTowardsZero:
case ZEND_ENUM_RoundingMode_PositiveInfinity:
case ZEND_ENUM_RoundingMode_NegativeInfinity:
case ZEND_ENUM_RoundingMode_AwayFromZero:
goto check_zero;
case ZEND_ENUM_RoundingMode_HalfEven:
if (rounded_len == 0 || num->n_value[rounded_len - 1] % 2 == 0) {
goto check_zero;
}
break;
case ZEND_ENUM_RoundingMode_HalfOdd:
if (rounded_len != 0 && num->n_value[rounded_len - 1] % 2 == 1) {
goto check_zero;
}
break;
default: ZEND_UNREACHABLE();
}
up:
{
bc_num tmp;
if (rounded_len == 0) {
tmp = bc_new_num(num->n_len + 1, 0);
tmp->n_value[0] = 1;
tmp->n_sign = num->n_sign;
} else {
bc_num scaled_one = bc_new_num((*result)->n_len, (*result)->n_scale);
scaled_one->n_value[rounded_len - 1] = 1;
tmp = _bc_do_add(*result, scaled_one);
tmp->n_sign = (*result)->n_sign;
bc_free_num(&scaled_one);
}
bc_free_num(result);
*result = tmp;
}
check_zero:
{
size_t scale = (*result)->n_scale;
if (bc_is_zero(*result)) {
(*result)->n_sign = PLUS;
(*result)->n_scale = 0;
}
return scale;
}
}