/
vshmidt
/
php
Обзор
Документация
Войти
/
vshmidt
/
php
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ext/bcmath/libbcmath/src/round.c
234 строки
6 KB
Saki Takamachi
ext/bcmath: If the result is `0`, `n_scale` is set to `0`. (#18056)
14 мар 2025, 11:52
Не верифицирован
14 мар 2025, 11:52
32547f1
Код
Авторство
О чём код?
/* +----------------------------------------------------------------------+ | 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> /* Returns the scale of the value after rounding. */ size_t 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 0; case PHP_ROUND_CEILING: if (num->n_sign == MINUS) { *result = bc_copy_num(BCG(_zero_)); return 0; } break; case PHP_ROUND_FLOOR: if (num->n_sign == PLUS) { *result = bc_copy_num(BCG(_zero_)); return 0; } break; case PHP_ROUND_AWAY_FROM_ZERO: break; EMPTY_SWITCH_DEFAULT_CASE() } if (bc_is_zero(num)) { *result = bc_copy_num(BCG(_zero_)); return 0; } /* 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 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 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: { size_t scale = (*result)->n_scale; if (bc_is_zero(*result)) { (*result)->n_sign = PLUS; (*result)->n_scale = 0; } return scale; } }