From a5e6d92bbbe31d33814d3c554271b226647cbc24 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sun, 19 Jul 2026 23:03:45 +0800 Subject: [PATCH 1/9] ext/gmp: Add gmp_prevprime() --- NEWS | 1 + UPGRADING | 7 +++++ ext/gmp/config.m4 | 1 + ext/gmp/config.w32 | 3 +++ ext/gmp/gmp.c | 5 ++++ ext/gmp/gmp.stub.php | 4 +++ ext/gmp/gmp_arginfo.h | 14 +++++++++- ext/gmp/tests/bug80560.phpt | 3 +++ ext/gmp/tests/gh13661.phpt | 51 +++++++++++++++++++++++++++++++++++++ 9 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 ext/gmp/tests/gh13661.phpt diff --git a/NEWS b/NEWS index 0ad622eec829..99a3b2089e8f 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,7 @@ PHP NEWS . Implemented partial function application RFC. (Arnaud) - GMP: + . Added gmp_prevprime(). (Weilin Du, David Carlier) . Fixed GMP power and shift operators to reject GMP right operands outside the unsigned long range instead of silently truncating them. (Weilin Du) . Fixed GMP integer string parsing to reject strings containing NUL bytes diff --git a/UPGRADING b/UPGRADING index 1b7cda4b8612..aa0ba5265fa6 100644 --- a/UPGRADING +++ b/UPGRADING @@ -260,6 +260,10 @@ PHP 8.6 UPGRADE NOTES - Fileinfo: . finfo_file() now works with remote streams. +- GMP: + . Added gmp_prevprime() to get the largest prime smaller than the given + number. Requires GNU MP 6.3.0 or later. + - Intl: . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(), with the alias of locale_get_display_keyword() and @@ -401,6 +405,9 @@ PHP 8.6 UPGRADE NOTES 6. New Functions ======================================== +- GMP: + . gmp_prevprime() + - Intl: . grapheme_strrev() RFC: https://wiki.php.net/rfc/grapheme_strrev diff --git a/ext/gmp/config.m4 b/ext/gmp/config.m4 index f0f07d377078..c4ffc2d42d4d 100644 --- a/ext/gmp/config.m4 +++ b/ext/gmp/config.m4 @@ -22,6 +22,7 @@ if test "$PHP_GMP" != "no"; then LIBS="$LIBS $GMP_LIBS" gmp_check=no AC_CHECK_HEADER([gmp.h], [AC_CHECK_FUNC([__gmpz_rootrem], [gmp_check=yes])]) + AC_CHECK_FUNCS([__gmpz_prevprime]) CFLAGS=$CFLAGS_SAVED LIBS=$LIBS_SAVED diff --git a/ext/gmp/config.w32 b/ext/gmp/config.w32 index dc0c1e978d31..d83048ebe9c6 100644 --- a/ext/gmp/config.w32 +++ b/ext/gmp/config.w32 @@ -5,6 +5,9 @@ ARG_WITH("gmp", "Include GNU MP support.", "no"); if (PHP_GMP != "no") { if (CHECK_LIB("mpir_a.lib", "gmp", PHP_GMP) && CHECK_HEADER("gmp.h", "CFLAGS_GMP", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { + if (GREP_HEADER("gmp.h", "mpz_prevprime", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) { + AC_DEFINE('HAVE___GMPZ_PREVPRIME', 1, "Define to 1 if GMP has the 'mpz_prevprime' function."); + } EXTENSION("gmp", "gmp.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); PHP_INSTALL_HEADERS("ext/gmp", "php_gmp_int.h"); AC_DEFINE('HAVE_GMP', 1, "Define to 1 if the PHP extension 'gmp' is available."); diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index a5cbdba3484e..ce3f5fd46668 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1082,6 +1082,11 @@ GMP_UNARY_OP_FUNCTION(com); /* {{{ Finds next prime of a */ GMP_UNARY_OP_FUNCTION(nextprime); +#ifdef HAVE___GMPZ_PREVPRIME +/* {{{ Finds previous prime of a */ +GMP_UNARY_OP_FUNCTION(prevprime); +#endif + /* Add a and b */ GMP_BINARY_OP_FUNCTION(add); /* Subtract b from a */ diff --git a/ext/gmp/gmp.stub.php b/ext/gmp/gmp.stub.php index 75812c62c5ca..d3c603310a4a 100644 --- a/ext/gmp/gmp.stub.php +++ b/ext/gmp/gmp.stub.php @@ -183,4 +183,8 @@ function gmp_hamdist(GMP|int|string $num1, GMP|int|string $num2): int {} function gmp_nextprime(GMP|int|string $num): GMP {} +#ifdef HAVE___GMPZ_PREVPRIME +function gmp_prevprime(GMP|int|string $num): GMP {} +#endif + function gmp_binomial(GMP|int|string $n, int $k): GMP {} diff --git a/ext/gmp/gmp_arginfo.h b/ext/gmp/gmp_arginfo.h index 436e3a22ea72..957fdc8d7765 100644 --- a/ext/gmp/gmp_arginfo.h +++ b/ext/gmp/gmp_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit gmp.stub.php instead. - * Stub hash: 3aabd5a5d2db0df15b249a425465ae718c13ab6b */ + * Stub hash: 9d651cc4ba238a496ebe8302fe3e0f985d48d769 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_init, 0, 1, GMP, 0) ZEND_ARG_TYPE_MASK(0, num, MAY_BE_LONG|MAY_BE_STRING, NULL) @@ -179,6 +179,12 @@ ZEND_END_ARG_INFO() #define arginfo_gmp_nextprime arginfo_gmp_neg +#if defined(HAVE___GMPZ_PREVPRIME) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_prevprime, 0, 1, GMP, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, num, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_binomial, 0, 2, GMP, 0) ZEND_ARG_OBJ_TYPE_MASK(0, n, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL) ZEND_ARG_TYPE_INFO(0, k, IS_LONG, 0) @@ -245,6 +251,9 @@ ZEND_FUNCTION(gmp_scan1); ZEND_FUNCTION(gmp_popcount); ZEND_FUNCTION(gmp_hamdist); ZEND_FUNCTION(gmp_nextprime); +#if defined(HAVE___GMPZ_PREVPRIME) +ZEND_FUNCTION(gmp_prevprime); +#endif ZEND_FUNCTION(gmp_binomial); ZEND_METHOD(GMP, __construct); ZEND_METHOD(GMP, __serialize); @@ -301,6 +310,9 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(gmp_popcount, arginfo_gmp_popcount) ZEND_FE(gmp_hamdist, arginfo_gmp_hamdist) ZEND_FE(gmp_nextprime, arginfo_gmp_nextprime) +#if defined(HAVE___GMPZ_PREVPRIME) + ZEND_FE(gmp_prevprime, arginfo_gmp_prevprime) +#endif ZEND_FE(gmp_binomial, arginfo_gmp_binomial) ZEND_FE_END }; diff --git a/ext/gmp/tests/bug80560.phpt b/ext/gmp/tests/bug80560.phpt index 25af4a42d9b9..c5ccc050070c 100644 --- a/ext/gmp/tests/bug80560.phpt +++ b/ext/gmp/tests/bug80560.phpt @@ -24,6 +24,9 @@ $functions1 = [ 'gmp_com', 'gmp_nextprime', ]; +if (function_exists('gmp_prevprime')) { + $functions1[] = 'gmp_prevprime'; +} $functions1_need_int_2 = [ 'gmp_testbit', 'gmp_scan0', diff --git a/ext/gmp/tests/gh13661.phpt b/ext/gmp/tests/gh13661.phpt new file mode 100644 index 000000000000..7a3268c30d56 --- /dev/null +++ b/ext/gmp/tests/gh13661.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-13661: Add gmp_prevprime() +--EXTENSIONS-- +gmp +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +object(GMP)#%d (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#%d (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#%d (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#%d (1) { + ["num"]=> + string(1) "0" +} +object(GMP)#%d (1) { + ["num"]=> + string(1) "2" +} +object(GMP)#%d (1) { + ["num"]=> + string(1) "3" +} +object(GMP)#%d (1) { + ["num"]=> + string(4) "9973" +} From 1b2f078778aef373e768201fdcf990e68bbc0f41 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 20 Jul 2026 16:06:20 +0800 Subject: [PATCH 2/9] add behavior to 0 and 1 --- UPGRADING | 3 ++- ext/gmp/gmp.c | 19 ++++++++++++- ext/gmp/tests/gh13661.phpt | 55 +++++++++++++------------------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/UPGRADING b/UPGRADING index aa0ba5265fa6..0d929f57a4fb 100644 --- a/UPGRADING +++ b/UPGRADING @@ -262,7 +262,8 @@ PHP 8.6 UPGRADE NOTES - GMP: . Added gmp_prevprime() to get the largest prime smaller than the given - number. Requires GNU MP 6.3.0 or later. + number. A ValueError is thrown if no such prime exists. Requires GNU MP + 6.3.0 or later. - Intl: . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(), diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index ce3f5fd46668..82284cc6933c 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1084,7 +1084,24 @@ GMP_UNARY_OP_FUNCTION(nextprime); #ifdef HAVE___GMPZ_PREVPRIME /* {{{ Finds previous prime of a */ -GMP_UNARY_OP_FUNCTION(prevprime); +ZEND_FUNCTION(gmp_prevprime) +{ + mpz_ptr gmpnum_a, gmpnum_result; + int res; + + ZEND_PARSE_PARAMETERS_START(1, 1) + GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) + ZEND_PARSE_PARAMETERS_END(); + + INIT_GMP_RETVAL(gmpnum_result); + res = mpz_prevprime(gmpnum_result, gmpnum_a); + if (!res) { + zval_ptr_dtor(return_value); + zend_argument_value_error(1, "must be greater than 2"); + RETURN_THROWS(); + } +} +/* }}} */ #endif /* Add a and b */ diff --git a/ext/gmp/tests/gh13661.phpt b/ext/gmp/tests/gh13661.phpt index 7a3268c30d56..a30ebd418a11 100644 --- a/ext/gmp/tests/gh13661.phpt +++ b/ext/gmp/tests/gh13661.phpt @@ -11,41 +11,24 @@ if (!function_exists('gmp_prevprime')) { --FILE-- getMessage() . \PHP_EOL; + } +} + +var_dump(gmp_strval(gmp_prevprime(3))); +var_dump(gmp_strval(gmp_prevprime(4))); +var_dump(gmp_strval(gmp_prevprime(10000))); ?> ---EXPECTF-- -object(GMP)#%d (1) { - ["num"]=> - string(1) "0" -} -object(GMP)#%d (1) { - ["num"]=> - string(1) "0" -} -object(GMP)#%d (1) { - ["num"]=> - string(1) "0" -} -object(GMP)#%d (1) { - ["num"]=> - string(1) "0" -} -object(GMP)#%d (1) { - ["num"]=> - string(1) "2" -} -object(GMP)#%d (1) { - ["num"]=> - string(1) "3" -} -object(GMP)#%d (1) { - ["num"]=> - string(4) "9973" -} +--EXPECT-- +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +gmp_prevprime(): Argument #1 ($num) must be greater than 2 +string(1) "2" +string(1) "3" +string(4) "9973" From cb714f3995c2e2686d471c715f34280cdfe99bce Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 20 Jul 2026 16:40:00 +0800 Subject: [PATCH 3/9] fix core dump --- ext/gmp/gmp.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 82284cc6933c..fcdae3e9aaf7 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1087,19 +1087,24 @@ GMP_UNARY_OP_FUNCTION(nextprime); ZEND_FUNCTION(gmp_prevprime) { mpz_ptr gmpnum_a, gmpnum_result; + mpz_t prevprime_result; int res; ZEND_PARSE_PARAMETERS_START(1, 1) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) ZEND_PARSE_PARAMETERS_END(); - INIT_GMP_RETVAL(gmpnum_result); - res = mpz_prevprime(gmpnum_result, gmpnum_a); + mpz_init(prevprime_result); + res = mpz_prevprime(prevprime_result, gmpnum_a); if (!res) { - zval_ptr_dtor(return_value); + mpz_clear(prevprime_result); zend_argument_value_error(1, "must be greater than 2"); RETURN_THROWS(); } + + INIT_GMP_RETVAL(gmpnum_result); + mpz_set(gmpnum_result, prevprime_result); + mpz_clear(prevprime_result); } /* }}} */ #endif From dd1374f6dfedd881aeb2c6dca47d947ea6ca3874 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 20 Jul 2026 16:45:47 +0800 Subject: [PATCH 4/9] Add comments to explain when mpz_prevprime ret 0 --- ext/gmp/gmp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index fcdae3e9aaf7..e2f628a8a76c 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1096,6 +1096,11 @@ ZEND_FUNCTION(gmp_prevprime) mpz_init(prevprime_result); res = mpz_prevprime(prevprime_result, gmpnum_a); + /* + * mpz_prevprime() returns 0 when no previous prime exists, which happens + * for operands less than 3. + * https://gmplib.org/manual/Number-Theoretic-Functions#index-mpz_005fprevprime + */ if (!res) { mpz_clear(prevprime_result); zend_argument_value_error(1, "must be greater than 2"); From 3f5c9fdf8bbc9166ef49ca2771e2a6f7b21335f3 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 20 Jul 2026 16:53:21 +0800 Subject: [PATCH 5/9] Rewrite the implementation Rewrite the implementation by hand because gpt is shit --- ext/gmp/gmp.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index e2f628a8a76c..25a695f01997 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1087,29 +1087,25 @@ GMP_UNARY_OP_FUNCTION(nextprime); ZEND_FUNCTION(gmp_prevprime) { mpz_ptr gmpnum_a, gmpnum_result; - mpz_t prevprime_result; int res; ZEND_PARSE_PARAMETERS_START(1, 1) GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_a) ZEND_PARSE_PARAMETERS_END(); - mpz_init(prevprime_result); - res = mpz_prevprime(prevprime_result, gmpnum_a); - /* - * mpz_prevprime() returns 0 when no previous prime exists, which happens - * for operands less than 3. - * https://gmplib.org/manual/Number-Theoretic-Functions#index-mpz_005fprevprime - */ - if (!res) { - mpz_clear(prevprime_result); + if (mpz_cmp_ui(gmpnum_a, 2) <= 0) { + /* + * mpz_prevprime() returns 0 when no previous prime exists, which happens + * for operands not greater than 2. + * https://gmplib.org/manual/Number-Theoretic-Functions#index-mpz_005fprevprime + */ zend_argument_value_error(1, "must be greater than 2"); RETURN_THROWS(); } INIT_GMP_RETVAL(gmpnum_result); - mpz_set(gmpnum_result, prevprime_result); - mpz_clear(prevprime_result); + res = mpz_prevprime(gmpnum_result, gmpnum_a); + ZEND_ASSERT(res); } /* }}} */ #endif From 0deba799d5bf035cf904aed716da097776717889 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 20 Jul 2026 17:08:16 +0800 Subject: [PATCH 6/9] Trigger PR update From d69639afcf7c84a2be5d0c9e7c2e0d1efcf76a10 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 20 Jul 2026 17:49:14 +0800 Subject: [PATCH 7/9] [skip ci] nit: fix test title --- ext/gmp/tests/gh13661.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/gmp/tests/gh13661.phpt b/ext/gmp/tests/gh13661.phpt index a30ebd418a11..42c08566c413 100644 --- a/ext/gmp/tests/gh13661.phpt +++ b/ext/gmp/tests/gh13661.phpt @@ -1,5 +1,5 @@ --TEST-- -GH-13661: Add gmp_prevprime() +gmp_prevprime() --EXTENSIONS-- gmp --SKIPIF-- From 2f1773982379ccc85d58ed6e7796cc1cd8ee465b Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 21 Jul 2026 17:20:58 +0800 Subject: [PATCH 8/9] [skip ci] Better comments --- ext/gmp/gmp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index 25a695f01997..e3ee2e9e5356 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1098,6 +1098,9 @@ ZEND_FUNCTION(gmp_prevprime) * mpz_prevprime() returns 0 when no previous prime exists, which happens * for operands not greater than 2. * https://gmplib.org/manual/Number-Theoretic-Functions#index-mpz_005fprevprime + * However. since returning a prime number smaller than 2 is mathemeticaly + * impossible, throwing a ValueError instead is a more widely used solution + * through out the php code base. */ zend_argument_value_error(1, "must be greater than 2"); RETURN_THROWS(); From ccec2ce0a81e7bdbcb2bb74866b61b6567a31deb Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 21 Jul 2026 17:21:34 +0800 Subject: [PATCH 9/9] [skip ci] Fix spelling error in comment in gmp.c Corrected a spelling error in a comment. --- ext/gmp/gmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c index e3ee2e9e5356..a5bd42b31305 100644 --- a/ext/gmp/gmp.c +++ b/ext/gmp/gmp.c @@ -1100,7 +1100,7 @@ ZEND_FUNCTION(gmp_prevprime) * https://gmplib.org/manual/Number-Theoretic-Functions#index-mpz_005fprevprime * However. since returning a prime number smaller than 2 is mathemeticaly * impossible, throwing a ValueError instead is a more widely used solution - * through out the php code base. + * throughout the php code base. */ zend_argument_value_error(1, "must be greater than 2"); RETURN_THROWS();