【PHP8.3】PHP的随机数生成算法得到进一步改进
在PHP 8.2中,随机数得到了大幅改善,但很快在PHP 8.3中,决定增加了一些新功能。
以下是RFC(Randomizer Additions)的介绍。
PHP RFC: 随机数生成器 添加功能
介绍在这份RFC中,我们建议向\Random\Randomizer中添加一些在用户空间中实施起来困难或繁琐的有用功能。
在创建识别码、兑换码和超出整数范围的数字字符串等用途中,经常需要生成包含特定字符的随机字符串。
要在用户界面实现此操作,需要在循环中获取输入字符串的随机偏移量,尽管这很简单,但却需要多行代码。
而且,还容易出现微小的错误,如忘记从字符长度中减去1等。
在生成随机浮点数方面非常有用,例如在需要按照一定概率生成布尔值的情况下。
在用户层面执行此操作看似简单,实际上却很繁琐。
常见的做法是通过Randomizer::getInt()获取的值来进行除法计算,但由于四舍五入误差和浮点数偏差等原因,结果往往不太准确。
提案在\ Random \ Randomizer中添加3个方法和一个枚举。
namespace Random;
final class Randomizer {
// […]
public function getBytesFromString(string $string, int $length): string {}
public function nextFloat(): float {}
public function getFloat(
float $min,
float $max,
IntervalBoundary $boundary = IntervalBoundary::ClosedOpen
): float {}
}
enum IntervalBoundary {
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
}
将字符串转换为字节从给定的字符串中生成随机字符串。
namespace Random;
final class Randomizer {
// […]
public function getBytesFromString(string $string, int $length): string {}
public function nextFloat(): float {}
public function getFloat(
float $min,
float $max,
IntervalBoundary $boundary = IntervalBoundary::ClosedOpen
): float {}
}
enum IntervalBoundary {
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
}
如果第一个参数$string是一个选择字符串,则当存在相同字符时,该字符被选择的概率会增加。如果所有字符只出现一次,那么选择概率是均匀的。
第二个参数$length表示返回值的长度。
例子
$randomizer = new \Random\Randomizer();
// ランダムなドメイン名
var_dump(sprintf(
"%s.example.com",
$randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16)
)); // string(28) "xfhnr0z6ok5fdlbz.example.com"
// 多要素認証の認証コード
var_dump(
implode('-', str_split($randomizer->getBytesFromString('0123456789', 20), 5))
); // string(23) "09898-46592-79230-33336"
// 小数
var_dump(sprintf(
'0.%s',
$randomizer->getBytesFromString('0123456789', 30)
)); // string(30) "0.217312509790167227890877670844"
// aが75%、bが25%で出現するランダム文字列
var_dump(
$randomizer->getBytesFromString('aaab', 16)
); // string(16) "baabaaaaaaababaa"
// DNA
var_dump(
$randomizer->getBytesFromString('ACGT', 30)
); // string(30) "CGTAGATCGTTCTGATAGAAGCTAACGGTT"
$randomizer = new \Random\Randomizer();
// ランダムなドメイン名
var_dump(sprintf(
"%s.example.com",
$randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16)
)); // string(28) "xfhnr0z6ok5fdlbz.example.com"
// 多要素認証の認証コード
var_dump(
implode('-', str_split($randomizer->getBytesFromString('0123456789', 20), 5))
); // string(23) "09898-46592-79230-33336"
// 小数
var_dump(sprintf(
'0.%s',
$randomizer->getBytesFromString('0123456789', 30)
)); // string(30) "0.217312509790167227890877670844"
// aが75%、bが25%で出現するランダム文字列
var_dump(
$randomizer->getBytesFromString('aaab', 16)
); // string(16) "baabaaaaaaababaa"
// DNA
var_dump(
$randomizer->getBytesFromString('ACGT', 30)
); // string(30) "CGTAGATCGTTCTGATAGAAGCTAACGGTT"
在创建小数时,请注意最后一位可能为0。
获取浮点数()返回介于参数 $min 和 $max 之间的小数。
区间的边界开放与否取决于参数 $boundary。
默认情况下是半开区间 [$min, $max),包括 $min 但不包括 $max。
返回的值在设定的区间内均匀分布。
在均等分布中,每个区间的值所占比例与其他相同宽度的区间的值所占比例相等。
例如,当调用getFloat(0, 1, IntervalBoundary::ClosedOpen)时,返回值小于0.5的概率与大于或等于0.5的概率相同。
类似地,小于0.1的概率为10%,与大于或等于0.9的概率相同。
我們使用的演算法是來自一篇名為「從區間中提取隨機浮點數的γ-section」的論文中公開的γ-section演算法。
第一个参数$min表示最小值。
第二个参数$max表示最大值。
第三个参数$boundary表示返回值中边界的处理方式。
– 默认为\Random\IntervalBoundary::ClosedOpen,表示[$min, $max)。
– ClosedClosed表示[$min, $max]。
– ClosedOpen表示($min, $max]。
– OpenOpen表示($min, $max)。
示例
$randomizer = new \Random\Randomizer();
// 経緯度
// 緯度は90/-90どちらも可
// 経度は180はあるけど-180はない
var_dump(sprintf(
"Lat: %+.6f Lng: %+.6f",
$randomizer->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
)); // string(32) "Lat: -51.742529 Lng: +135.396328"
下一个浮点数()这个方法与->getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen)是相同的。
它可以更快地处理常见的情况,其中希望获取范围为[0, 1)的数据。
例子
$randomizer = new \Random\Randomizer();
// 50%の確率
var_dump(
$randomizer->nextFloat() < 0.5
); // bool(true)
// 10%の確率
var_dump(
$randomizer->nextFloat() < 0.1
); // bool(false)
向后不兼容的更改类名\Random\IntervalBoundary将无法使用。
$randomizer = new \Random\Randomizer();
// 経緯度
// 緯度は90/-90どちらも可
// 経度は180はあるけど-180はない
var_dump(sprintf(
"Lat: %+.6f Lng: %+.6f",
$randomizer->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
)); // string(32) "Lat: -51.742529 Lng: +135.396328"
它可以更快地处理常见的情况,其中希望获取范围为[0, 1)的数据。
例子
$randomizer = new \Random\Randomizer();
// 50%の確率
var_dump(
$randomizer->nextFloat() < 0.5
); // bool(true)
// 10%の確率
var_dump(
$randomizer->nextFloat() < 0.1
); // bool(false)
向后不兼容的更改类名\Random\IntervalBoundary将无法使用。
$randomizer = new \Random\Randomizer();
// 50%の確率
var_dump(
$randomizer->nextFloat() < 0.5
); // bool(true)
// 10%の確率
var_dump(
$randomizer->nextFloat() < 0.1
); // bool(false)
命名空间\Random是在PHP的随机数中使用的,而且在GitHub上也没有同名的类,所以不会有实际影响。
建议的 PHP 版本PHP8.x 可以进行一种本地化的中国语言释义,但只提供一种选项 :PHP8.x
RFC 影响对于SAPI和扩展模块,以及常量和php.ini的更改,没有任何影响。
提出的投票选择getBytesFromString()被以18票赞成、0票反对通过,getFloat()/ nextFloat()被以16票赞成、1票反对的赞成多数通过。
此功能将在PHP8.3中引入。
补丁和测试getBytesFromString()方法的实现
getFloat()/nextFloat()方法的实现
执行实施https://github.com/php/php-src/commit/ac3ecd03af009d433d4b75d570b3b0f0a3fc0ff7 可以被表述为:
这是一个具有唯一标识符ac3ecd03af009d433d4b75d570b3b0f0a3fc0ff7的提交。
https://github.com/php/php-src/commit/f9a1a903805a0c260c97bcc8bf2c14f2dd76ca76 可以被表述为:
这是一个具有唯一标识符f9a1a903805a0c260c97bcc8bf2c14f2dd76ca76的提交。
参考资料最初的提案
RFC的讨论
感想是什么随机字符串有时候真的很麻烦。
常见的例子是str_shuffle,但它在密码学上不安全,所以并不太适合。
random_bytes是安全的,但返回的是二进制数据,如果想限制为[0-9A-Za-z]时使用起来会有些困难。
提出的投票选择getBytesFromString()被以18票赞成、0票反对通过,getFloat()/ nextFloat()被以16票赞成、1票反对的赞成多数通过。
此功能将在PHP8.3中引入。
补丁和测试getBytesFromString()方法的实现
getFloat()/nextFloat()方法的实现
执行实施https://github.com/php/php-src/commit/ac3ecd03af009d433d4b75d570b3b0f0a3fc0ff7 可以被表述为:
这是一个具有唯一标识符ac3ecd03af009d433d4b75d570b3b0f0a3fc0ff7的提交。
https://github.com/php/php-src/commit/f9a1a903805a0c260c97bcc8bf2c14f2dd76ca76 可以被表述为:
这是一个具有唯一标识符f9a1a903805a0c260c97bcc8bf2c14f2dd76ca76的提交。
参考资料最初的提案
RFC的讨论
感想是什么随机字符串有时候真的很麻烦。
常见的例子是str_shuffle,但它在密码学上不安全,所以并不太适合。
random_bytes是安全的,但返回的是二进制数据,如果想限制为[0-9A-Za-z]时使用起来会有些困难。
getFloat()/nextFloat()方法的实现
执行实施https://github.com/php/php-src/commit/ac3ecd03af009d433d4b75d570b3b0f0a3fc0ff7 可以被表述为:
这是一个具有唯一标识符ac3ecd03af009d433d4b75d570b3b0f0a3fc0ff7的提交。
https://github.com/php/php-src/commit/f9a1a903805a0c260c97bcc8bf2c14f2dd76ca76 可以被表述为:
这是一个具有唯一标识符f9a1a903805a0c260c97bcc8bf2c14f2dd76ca76的提交。
参考资料最初的提案
RFC的讨论
感想是什么随机字符串有时候真的很麻烦。
常见的例子是str_shuffle,但它在密码学上不安全,所以并不太适合。
random_bytes是安全的,但返回的是二进制数据,如果想限制为[0-9A-Za-z]时使用起来会有些困难。
RFC的讨论
感想是什么随机字符串有时候真的很麻烦。
常见的例子是str_shuffle,但它在密码学上不安全,所以并不太适合。
random_bytes是安全的,但返回的是二进制数据,如果想限制为[0-9A-Za-z]时使用起来会有些困难。
因此,我们已经添加了一种实用且方便的方法。
今后,要求创建一个安全的密码,可以很容易地实现。
顺便提一下,根据这个RFC,似乎不是Random Extension 5x和Random Extension Improvement的创作者参与了其中,而是那些在邮件列表上对这些RFC提出了宝贵建议的人制作了它们。