xref: /php-src/ext/random/gammasection.c (revision 79133df1)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Tim Düsterhus <timwolla@php.net>                            |
14    |                                                                      |
15    | Based on code from: Frédéric Goualard                                |
16    | Corrected to handled appropriately random integers larger than 2^53  |
17    | converted to double precision floats, and to avoid overflows for     |
18    | large gammas.                                                        |
19    +----------------------------------------------------------------------+
20 */
21 
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25 
26 #include "php.h"
27 #include "php_random.h"
28 #include <math.h>
29 
30 /* This file implements the γ-section algorithm as published in:
31  *
32  * Drawing Random Floating-Point Numbers from an Interval. Frédéric
33  * Goualard, ACM Trans. Model. Comput. Simul., 32:3, 2022.
34  * https://doi.org/10.1145/3503512
35  */
36 
gamma_low(double x)37 static double gamma_low(double x)
38 {
39 	return x - nextafter(x, -DBL_MAX);
40 }
41 
gamma_high(double x)42 static double gamma_high(double x)
43 {
44 	return nextafter(x, DBL_MAX) - x;
45 }
46 
gamma_max(double x,double y)47 static double gamma_max(double x, double y)
48 {
49 	return (fabs(x) > fabs(y)) ? gamma_high(x) : gamma_low(y);
50 }
51 
splitint64(uint64_t v,double * vhi,double * vlo)52 static void splitint64(uint64_t v, double *vhi, double *vlo)
53 {
54 	*vhi = v >> 2;
55 	*vlo = v & UINT64_C(0x3);
56 }
57 
ceilint(double a,double b,double g)58 static uint64_t ceilint(double a, double b, double g)
59 {
60 	double s = b / g - a / g;
61 	double e;
62 
63 	if (fabs(a) <= fabs(b)) {
64 		e = -a / g - (s - b / g);
65 	} else {
66 		e = b / g - (s + a / g);
67 	}
68 
69 	double si = ceil(s);
70 
71 	return (s != si) ? (uint64_t)si : (uint64_t)si + (e > 0);
72 }
73 
php_random_gammasection_closed_open(php_random_algo_with_state engine,double min,double max)74 PHPAPI double php_random_gammasection_closed_open(php_random_algo_with_state engine, double min, double max)
75 {
76 	double g = gamma_max(min, max);
77 	uint64_t hi = ceilint(min, max, g);
78 
79 	if (UNEXPECTED(max <= min || hi < 1)) {
80 		return NAN;
81 	}
82 
83 	uint64_t k = 1 + php_random_range64(engine, hi - 1); /* [1, hi] */
84 
85 	if (fabs(min) <= fabs(max)) {
86 		if (k == hi) {
87 			return min;
88 		} else {
89 			double k_hi, k_lo;
90 			splitint64(k, &k_hi, &k_lo);
91 
92 			return 0x1p+2 * (max * 0x1p-2 - k_hi * g) - k_lo * g;
93 		}
94 	} else {
95 		double k_hi, k_lo;
96 		splitint64(k - 1, &k_hi, &k_lo);
97 
98 		return 0x1p+2 * (min * 0x1p-2 + k_hi * g) + k_lo * g;
99 	}
100 }
101 
php_random_gammasection_closed_closed(php_random_algo_with_state engine,double min,double max)102 PHPAPI double php_random_gammasection_closed_closed(php_random_algo_with_state engine, double min, double max)
103 {
104 	double g = gamma_max(min, max);
105 	uint64_t hi = ceilint(min, max, g);
106 
107 	if (UNEXPECTED(max < min)) {
108 		return NAN;
109 	}
110 
111 	uint64_t k = php_random_range64(engine, hi); /* [0, hi] */
112 
113 	if (fabs(min) <= fabs(max)) {
114 		if (k == hi) {
115 			return min;
116 		} else {
117 			double k_hi, k_lo;
118 			splitint64(k, &k_hi, &k_lo);
119 
120 			return 0x1p+2 * (max * 0x1p-2 - k_hi * g) - k_lo * g;
121 		}
122 	} else {
123 		if (k == hi) {
124 			return max;
125 		} else {
126 			double k_hi, k_lo;
127 			splitint64(k, &k_hi, &k_lo);
128 
129 			return 0x1p+2 * (min * 0x1p-2 + k_hi * g) + k_lo * g;
130 		}
131 	}
132 }
133 
php_random_gammasection_open_closed(php_random_algo_with_state engine,double min,double max)134 PHPAPI double php_random_gammasection_open_closed(php_random_algo_with_state engine, double min, double max)
135 {
136 	double g = gamma_max(min, max);
137 	uint64_t hi = ceilint(min, max, g);
138 
139 	if (UNEXPECTED(max <= min || hi < 1)) {
140 		return NAN;
141 	}
142 
143 	uint64_t k = php_random_range64(engine, hi - 1); /* [0, hi - 1] */
144 
145 	if (fabs(min) <= fabs(max)) {
146 		double k_hi, k_lo;
147 		splitint64(k, &k_hi, &k_lo);
148 
149 		return 0x1p+2 * (max * 0x1p-2 - k_hi * g) - k_lo * g;
150 	} else {
151 		if (k == (hi - 1)) {
152 			return max;
153 		} else {
154 			double k_hi, k_lo;
155 			splitint64(k + 1, &k_hi, &k_lo);
156 
157 			return 0x1p+2 * (min * 0x1p-2 + k_hi * g) + k_lo * g;
158 		}
159 	}
160 }
161 
php_random_gammasection_open_open(php_random_algo_with_state engine,double min,double max)162 PHPAPI double php_random_gammasection_open_open(php_random_algo_with_state engine, double min, double max)
163 {
164 	double g = gamma_max(min, max);
165 	uint64_t hi = ceilint(min, max, g);
166 
167 	if (UNEXPECTED(max <= min || hi < 2)) {
168 		return NAN;
169 	}
170 
171 	uint64_t k = 1 + php_random_range64(engine, hi - 2); /* [1, hi - 1] */
172 
173 	if (fabs(min) <= fabs(max)) {
174 		double k_hi, k_lo;
175 		splitint64(k, &k_hi, &k_lo);
176 
177 		return 0x1p+2 * (max * 0x1p-2 - k_hi * g) - k_lo * g;
178 	} else {
179 		double k_hi, k_lo;
180 		splitint64(k, &k_hi, &k_lo);
181 
182 		return 0x1p+2 * (min * 0x1p-2 + k_hi * g) + k_lo * g;
183 	}
184 }
185