[php-src] master: ext/gmp: Add gmp_prevprime() (#22807)

Weilin Du via GitHub <[email protected]> Tue, 28 Jul 2026 08:38:21 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-07-28T16:38:19+08:00

Commit: https://github.com/php/php-src/commit/7abd4931d67a64fd76fd111b9f6ac885b9b018c5
Raw diff: https://github.com/php/php-src/commit/7abd4931d67a64fd76fd111b9f6ac885b9b018c5.diff

ext/gmp: Add gmp_prevprime() (#22807)

Expose mpz_prevprime() to the userland, namely gmp_prevprime().

Changed paths:
  A  ext/gmp/tests/gmp_prevprime.phpt
  M  NEWS
  M  UPGRADING
  M  ext/gmp/config.m4
  M  ext/gmp/config.w32
  M  ext/gmp/gmp.c
  M  ext/gmp/gmp.stub.php
  M  ext/gmp/gmp_arginfo.h
  M  ext/gmp/tests/bug80560.phpt


Diff:

diff --git a/NEWS b/NEWS
index 7ec4eb4b809e..a7aa29cd9043 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 d30a5663274f..3d9cfdc6aeb9 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 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 7f8799a7ae2d..9ade8eade215 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 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/gmp_prevprime.phpt b/ext/gmp/tests/gmp_prevprime.phpt
new file mode 100644
index 000000000000..42c08566c413
--- /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"