Skip to content

[RFC] Add bcfloor, bcceil and bcround to BCMath #13096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Zend/Optimizer/zend_func_infos.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ static const func_info_t func_infos[] = {
F1("bcpowmod", MAY_BE_STRING),
F1("bcpow", MAY_BE_STRING),
F1("bcsqrt", MAY_BE_STRING),
F1("bcfloor", MAY_BE_STRING),
F1("bcceil", MAY_BE_STRING),
F1("bcround", MAY_BE_STRING),
FN("bzopen", MAY_BE_RESOURCE|MAY_BE_FALSE),
F1("bzerror", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING),
F1("cal_from_jd", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_NULL),
Expand Down
90 changes: 90 additions & 0 deletions ext/bcmath/bcmath.c
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,96 @@ PHP_FUNCTION(bccomp)
}
/* }}} */

/* {{{ floor or ceil */
static void bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAMETERS, bool is_floor)
{
zend_string *numstr;
bc_num num, result;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STR(numstr)
ZEND_PARSE_PARAMETERS_END();

bc_init_num(&num);
bc_init_num(&result);

if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I miss tests for the error cases (this one and a few more below)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking!
I added value error case to tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a case with a nul byte in the string. The current API doesn't support them as it basically ignores anything past the nul byte.

goto cleanup;
}

bc_floor_or_ceil(num, is_floor, &result);
RETVAL_STR(bc_num2str_ex(result, 0));

cleanup: {
bc_free_num(&num);
bc_free_num(&result);
};
}
/* }}} */

/* {{{ Returns floor of num */
PHP_FUNCTION(bcfloor)
{
bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
}
/* }}} */

/* {{{ Returns ceil of num */
PHP_FUNCTION(bcceil)
{
bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
}
/* }}} */

/* {{{ Returns num rounded to the digits specified by precision. */
PHP_FUNCTION(bcround)
{
zend_string *numstr;
zend_long precision = 0;
zend_long mode = PHP_ROUND_HALF_UP;
bc_num num, result;

ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_STR(numstr)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(precision)
Z_PARAM_LONG(mode)
ZEND_PARSE_PARAMETERS_END();

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_CEILING:
case PHP_ROUND_FLOOR:
case PHP_ROUND_TOWARD_ZERO:
case PHP_ROUND_AWAY_FROM_ZERO:
break;
default:
zend_argument_value_error(3, "must be a valid rounding mode (PHP_ROUND_*)");
return;
}

bc_init_num(&num);
bc_init_num(&result);

if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

bc_round(num, precision, mode, &result);
RETVAL_STR(bc_num2str_ex(result, result->n_scale));

cleanup: {
bc_free_num(&num);
bc_free_num(&result);
};
}
/* }}} */

/* {{{ Sets default scale parameter for all bc math functions */
PHP_FUNCTION(bcscale)
{
Expand Down
9 changes: 9 additions & 0 deletions ext/bcmath/bcmath.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ function bcsqrt(string $num, ?int $scale = null): string {}
function bccomp(string $num1, string $num2, ?int $scale = null): int {}

function bcscale(?int $scale = null): int {}

/** @refcount 1 */
function bcfloor(string $num): string {}

/** @refcount 1 */
function bcceil(string $num): string {}

/** @refcount 1 */
function bcround(string $num, int $precision = 0, int $mode = PHP_ROUND_HALF_UP): string {}
20 changes: 19 additions & 1 deletion ext/bcmath/bcmath_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ext/bcmath/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ if test "$PHP_BCMATH" != "no"; then
PHP_NEW_EXTENSION(bcmath, bcmath.c \
libbcmath/src/add.c libbcmath/src/div.c libbcmath/src/init.c libbcmath/src/neg.c libbcmath/src/raisemod.c libbcmath/src/sub.c \
libbcmath/src/compare.c libbcmath/src/divmod.c libbcmath/src/int2num.c libbcmath/src/num2long.c libbcmath/src/output.c libbcmath/src/recmul.c \
libbcmath/src/sqrt.c libbcmath/src/zero.c libbcmath/src/doaddsub.c libbcmath/src/nearzero.c libbcmath/src/num2str.c libbcmath/src/raise.c \
libbcmath/src/rmzero.c libbcmath/src/str2num.c,
libbcmath/src/sqrt.c libbcmath/src/zero.c libbcmath/src/doaddsub.c libbcmath/src/floor_or_ceil.c libbcmath/src/nearzero.c libbcmath/src/num2str.c \
libbcmath/src/raise.c libbcmath/src/rmzero.c libbcmath/src/round.c libbcmath/src/str2num.c,
$ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
PHP_ADD_BUILD_DIR($ext_builddir/libbcmath/src)
AC_DEFINE(HAVE_BCMATH, 1, [Whether you have bcmath])
Expand Down
3 changes: 2 additions & 1 deletion ext/bcmath/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ if (PHP_BCMATH == "yes") {
ADD_SOURCES("ext/bcmath/libbcmath/src", "add.c div.c init.c neg.c \
raisemod.c sub.c compare.c divmod.c int2num.c \
num2long.c output.c recmul.c sqrt.c zero.c doaddsub.c \
nearzero.c num2str.c raise.c rmzero.c str2num.c", "bcmath");
floor_or_ceil.c nearzero.c num2str.c raise.c rmzero.c str2num.c \
round.c", "bcmath");

AC_DEFINE('HAVE_BCMATH', 1, 'Have BCMATH library');
}
8 changes: 7 additions & 1 deletion ext/bcmath/libbcmath/src/bcmath.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ typedef struct bc_struct {
#include "zend.h"
#include <stdbool.h>
#include "zend_string.h"
#include "../../php_bcmath.h" /* Needed for BCG() macro */

/* Needed for BCG() macro and PHP_ROUND_XXX */
#include "../../php_bcmath.h"

/* The base used in storing the numbers in n_value above.
Currently, this MUST be 10. */
Expand Down Expand Up @@ -125,6 +127,10 @@ bool bc_modulo(bc_num num1, bc_num num2, bc_num *resul, size_t scale);

bool bc_divmod(bc_num num1, bc_num num2, bc_num *quo, bc_num *rem, size_t scale);

void bc_floor_or_ceil(bc_num num, bool is_floor, bc_num *result);

void bc_round(bc_num num, zend_long places, zend_long mode, bc_num *result);

typedef enum {
OK,
BASE_HAS_FRACTIONAL,
Expand Down
57 changes: 57 additions & 0 deletions ext/bcmath/libbcmath/src/floor_or_ceil.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
+----------------------------------------------------------------------+
| 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 |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Saki Takamachi <[email protected]> |
+----------------------------------------------------------------------+
*/

#include "bcmath.h"
#include "private.h"
#include <stddef.h>

void bc_floor_or_ceil(bc_num num, bool is_floor, bc_num *result)
{
/* clear result */
bc_free_num(result);

/* Initialize result */
*result = bc_new_num(num->n_len, 0);
(*result)->n_sign = num->n_sign;

/* copy integer part */
memcpy((*result)->n_value, num->n_value, num->n_len);

/* If the number is positive and we are flooring, then nothing else needs to be done.
* Similarly, if the number is negative and we are ceiling, then nothing else needs to be done. */
if (num->n_scale == 0 || (*result)->n_sign == (is_floor ? PLUS : MINUS)) {
return;
}

/* check fractional part. */
size_t count = num->n_scale;
const char *nptr = num->n_value + num->n_len;
while ((count > 0) && (*nptr == 0)) {
count--;
nptr++;
}

/* If all digits past the decimal point are 0 */
if (count == 0) {
return;
}

/* Increment the absolute value of the result by 1 and add sign information */
bc_num tmp = _bc_do_add(*result, BCG(_one_), 0);
tmp->n_sign = (*result)->n_sign;
bc_free_num(result);
*result = tmp;
}
Loading