xref: /PHP-8.4/ext/bcmath/libbcmath/src/round.c (revision 674ec02e)
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