1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Saki Takamachi <saki@php.net> |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "bcmath.h"
18 #include "private.h"
19 #include <stddef.h>
20
bc_round(bc_num num,zend_long precision,zend_long mode,bc_num * result)21 void bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
22 {
23 /* clear result */
24 bc_free_num(result);
25
26 /*
27 * The following cases result in an early return:
28 *
29 * - When rounding to an integer part which is larger than the number
30 * e.g. Rounding 21.123 to 3 digits before the decimal point.
31 * - When rounding to a greater decimal precision then the number has, the number is unchanged
32 * e.g. Rounding 21.123 to 4 digits after the decimal point.
33 * - If the fractional part ends with zeros, the zeros are omitted and the number of digits in num is reduced.
34 * Meaning we might end up in the previous case.
35 */
36 if (precision < 0 && num->n_len < (size_t) (-(precision + Z_L(1))) + 1) {
37 *result = bc_copy_num(BCG(_zero_));
38 return;
39 }
40 /* Just like bcadd('1', '1', 4) becomes '2.0000', it pads with zeros at the end if necessary. */
41 if (precision >= 0 && num->n_scale <= precision) {
42 if (num->n_scale == precision) {
43 *result = bc_copy_num(num);
44 } else if(num->n_scale < precision) {
45 *result = bc_new_num(num->n_len, precision);
46 (*result)->n_sign = num->n_sign;
47 memcpy((*result)->n_value, num->n_value, num->n_len + num->n_scale);
48 }
49 return;
50 }
51
52 /*
53 * If the calculation result is a negative value, there is an early return,
54 * so no underflow will occur.
55 */
56 size_t rounded_len = num->n_len + precision;
57
58 /*
59 * Initialize result
60 * For example, if rounded_len is 0, it means trying to round 50 to 100 or 0.
61 * If the result of rounding is carried over, it will be added later, so first set it to 0 here.
62 */
63 if (rounded_len == 0) {
64 *result = bc_copy_num(BCG(_zero_));
65 } else {
66 *result = bc_new_num(num->n_len, precision > 0 ? precision : 0);
67 memcpy((*result)->n_value, num->n_value, rounded_len);
68 }
69 (*result)->n_sign = num->n_sign;
70
71 const char *nptr = num->n_value + rounded_len;
72
73 /* Check cases that can be determined without looping. */
74 switch (mode) {
75 case PHP_ROUND_HALF_UP:
76 if (*nptr >= 5) {
77 goto up;
78 } else if (*nptr < 5) {
79 goto check_zero;
80 }
81 break;
82
83 case PHP_ROUND_HALF_DOWN:
84 case PHP_ROUND_HALF_EVEN:
85 case PHP_ROUND_HALF_ODD:
86 if (*nptr > 5) {
87 goto up;
88 } else if (*nptr < 5) {
89 goto check_zero;
90 }
91 /* if *nptr == 5, we need to look-up further digits before making a decision. */
92 break;
93
94 case PHP_ROUND_CEILING:
95 if (num->n_sign != PLUS) {
96 goto check_zero;
97 } else if (*nptr > 0) {
98 goto up;
99 }
100 /* if *nptr == 0, a loop is required for judgment. */
101 break;
102
103 case PHP_ROUND_FLOOR:
104 if (num->n_sign != MINUS) {
105 goto check_zero;
106 } else if (*nptr > 0) {
107 goto up;
108 }
109 /* if *nptr == 0, a loop is required for judgment. */
110 break;
111
112 case PHP_ROUND_TOWARD_ZERO:
113 goto check_zero;
114
115 case PHP_ROUND_AWAY_FROM_ZERO:
116 if (*nptr > 0) {
117 goto up;
118 }
119 /* if *nptr == 0, a loop is required for judgment. */
120 break;
121
122 EMPTY_SWITCH_DEFAULT_CASE()
123 }
124
125 /* Loop through the remaining digits. */
126 size_t count = num->n_len + num->n_scale - rounded_len - 1;
127 nptr++;
128 while ((count > 0) && (*nptr == 0)) {
129 count--;
130 nptr++;
131 }
132
133 if (count > 0) {
134 goto up;
135 }
136
137 switch (mode) {
138 case PHP_ROUND_HALF_DOWN:
139 case PHP_ROUND_CEILING:
140 case PHP_ROUND_FLOOR:
141 case PHP_ROUND_AWAY_FROM_ZERO:
142 goto check_zero;
143
144 case PHP_ROUND_HALF_EVEN:
145 if (rounded_len == 0 || num->n_value[rounded_len - 1] % 2 == 0) {
146 goto check_zero;
147 }
148 break;
149
150 case PHP_ROUND_HALF_ODD:
151 if (rounded_len != 0 && num->n_value[rounded_len - 1] % 2 == 1) {
152 goto check_zero;
153 }
154 break;
155
156 EMPTY_SWITCH_DEFAULT_CASE()
157 }
158
159 up:
160 {
161 bc_num tmp;
162
163 if (rounded_len == 0) {
164 tmp = bc_new_num(num->n_len + 1, 0);
165 tmp->n_value[0] = 1;
166 tmp->n_sign = num->n_sign;
167 } else {
168 bc_num scaled_one = bc_new_num((*result)->n_len, (*result)->n_scale);
169 scaled_one->n_value[rounded_len - 1] = 1;
170
171 tmp = _bc_do_add(*result, scaled_one);
172 tmp->n_sign = (*result)->n_sign;
173 bc_free_num(&scaled_one);
174 }
175
176 bc_free_num(result);
177 *result = tmp;
178 }
179
180 check_zero:
181 if (bc_is_zero(*result)) {
182 (*result)->n_sign = PLUS;
183 }
184 }
185