1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00
Files
archived-php-src/ext/bcmath/libbcmath/src/round.c
2024-12-17 00:17:23 +09:00

229 lines
5.9 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Saki Takamachi <saki@php.net> |
+----------------------------------------------------------------------+
*/
#include "bcmath.h"
#include "private.h"
#include <stddef.h>
void bc_round(bc_num num, zend_long precision, zend_long 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 PHP_ROUND_HALF_UP:
case PHP_ROUND_HALF_DOWN:
case PHP_ROUND_HALF_EVEN:
case PHP_ROUND_HALF_ODD:
case PHP_ROUND_TOWARD_ZERO:
*result = bc_copy_num(BCG(_zero_));
return;
case PHP_ROUND_CEILING:
if (num->n_sign == MINUS) {
*result = bc_copy_num(BCG(_zero_));
return;
}
break;
case PHP_ROUND_FLOOR:
if (num->n_sign == PLUS) {
*result = bc_copy_num(BCG(_zero_));
return;
}
break;
case PHP_ROUND_AWAY_FROM_ZERO:
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
if (bc_is_zero(num)) {
*result = bc_copy_num(BCG(_zero_));
return;
}
/* If precision is -3, it becomes 1000. */
if (UNEXPECTED(precision == ZEND_LONG_MIN)) {
*result = bc_new_num((size_t) ZEND_LONG_MAX + 2, 0);
} else {
*result = bc_new_num(-precision + 1, 0);
}
(*result)->n_value[0] = 1;
(*result)->n_sign = num->n_sign;
return;
}
/* 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;
}
/*
* 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 PHP_ROUND_HALF_UP:
if (*nptr >= 5) {
goto up;
} else if (*nptr < 5) {
goto check_zero;
}
break;
case PHP_ROUND_HALF_DOWN:
case PHP_ROUND_HALF_EVEN:
case PHP_ROUND_HALF_ODD:
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 PHP_ROUND_CEILING:
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 PHP_ROUND_FLOOR:
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 PHP_ROUND_TOWARD_ZERO:
goto check_zero;
case PHP_ROUND_AWAY_FROM_ZERO:
if (*nptr > 0) {
goto up;
}
/* if *nptr == 0, a loop is required for judgment. */
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
/* 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 PHP_ROUND_HALF_DOWN:
case PHP_ROUND_CEILING:
case PHP_ROUND_FLOOR:
case PHP_ROUND_AWAY_FROM_ZERO:
goto check_zero;
case PHP_ROUND_HALF_EVEN:
if (rounded_len == 0 || num->n_value[rounded_len - 1] % 2 == 0) {
goto check_zero;
}
break;
case PHP_ROUND_HALF_ODD:
if (rounded_len != 0 && num->n_value[rounded_len - 1] % 2 == 1) {
goto check_zero;
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
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:
if (bc_is_zero(*result)) {
(*result)->n_sign = PLUS;
}
}