Commit 7abd4931d67 for php.net
commit 7abd4931d67a64fd76fd111b9f6ac885b9b018c5
Author: Weilin Du <weilindu@php.net>
Date: Tue Jul 28 16:38:19 2026 +0800
ext/gmp: Add gmp_prevprime() (#22807)
Expose mpz_prevprime() to the userland, namely gmp_prevprime().
diff --git a/NEWS b/NEWS
index 7ec4eb4b809..a7aa29cd904 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,7 @@ PHP NEWS
declares a default value for the attribute). (iliaal)
- 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 d30a5663274..3d9cfdc6aeb 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -271,6 +271,12 @@ 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. A ValueError is thrown if no such prime exists. This function is
+ available only when PHP is built against GNU MP 6.3.0 or later; it is not
+ available on official Windows builds using MPIR.
+
- Intl:
. Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(),
with the alias of locale_get_display_keyword() and
@@ -421,6 +427,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 f0f07d37707..c4ffc2d42d4 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 dc0c1e978d3..d83048ebe9c 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 7f8799a7ae2..9ade8eade21 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -1082,6 +1082,34 @@ GMP_UNARY_OP_FUNCTION(com);
/* {{{ Finds next prime of a */
GMP_UNARY_OP_FUNCTION(nextprime);
+#ifdef HAVE___GMPZ_PREVPRIME
+/* {{{ Finds previous prime of a */
+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();
+
+ 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);
+ res = mpz_prevprime(gmpnum_result, gmpnum_a);
+ ZEND_ASSERT(res);
+}
+/* }}} */
+#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 75812c62c5c..d3c603310a4 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 436e3a22ea7..957fdc8d776 100644
Binary files a/ext/gmp/gmp_arginfo.h and b/ext/gmp/gmp_arginfo.h differ
diff --git a/ext/gmp/tests/bug80560.phpt b/ext/gmp/tests/bug80560.phpt
index 25af4a42d9b..c5ccc050070 100644
--- a/ext/gmp/tests/bug80560.phpt
+++ b/ext/gmp/tests/bug80560.phpt
@@ -24,6 +24,9 @@
'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/gmp_prevprime.phpt b/ext/gmp/tests/gmp_prevprime.phpt
new file mode 100644
index 00000000000..42c08566c41
--- /dev/null
+++ b/ext/gmp/tests/gmp_prevprime.phpt
@@ -0,0 +1,34 @@
+--TEST--
+gmp_prevprime()
+--EXTENSIONS--
+gmp
+--SKIPIF--
+<?php
+if (!function_exists('gmp_prevprime')) {
+ die('skip gmp_prevprime() is not available');
+}
+?>
+--FILE--
+<?php
+
+foreach ([-1, 0, 1, 2] as $value) {
+ try {
+ var_dump(gmp_prevprime($value));
+ } catch (\ValueError $e) {
+ echo $e->getMessage() . \PHP_EOL;
+ }
+}
+
+var_dump(gmp_strval(gmp_prevprime(3)));
+var_dump(gmp_strval(gmp_prevprime(4)));
+var_dump(gmp_strval(gmp_prevprime(10000)));
+
+?>
+--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"