PHP-php的一个疑问
function dotrand($startnum,$endnum){
for ($i=$startnum;$i<=$endnum;$i=$i+0.1){
$re[]=$i;
}
return $re;
}
$c=dotrand(2.0,2.4);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
function dotrand($startnum,$endnum){
$l = ($endnum - $startnum) * 10.0 + 1e-10;
for ($i=0 ;(float)$i<=$l;$i=$i+1){
$re[]=$i/10.0 + $startnum;
}
return $re;
}
$c=dotrand(2.0,2.4);
试试这个吧,只能说是很大概率上解决了问题……
虽然说是精度问题,实际上这个问题包括了两个问题在内:
1. 0.1没办法精确量化成浮点立即数,你输入0.1实际上变成了0.99999999998或者0.1000000000002之类的数,这样连续累加之后就会出现严重的误差累积
2. 输入的起止参数也没法精确量化成浮点数,2.0是精确的,但2.4只能变成一个接近2.4的数,但不确定是比2.4小还是比2.4大
3. 隐含的舍入误差的问题,如果输入的$startnum = 1e-40,加完0.1之后就被舍掉了,最后仍然会跟输入0一样
这段代码解决了第一个问题,使用整数来作为循环变量,防止了循环过程中的误差累积;第二个问题通过额外增加一个1e-10的小的数来解决,其实这个不能算是解决只能算是权衡,认为程序员一般不会故意输入一个2.4-1e-12这样的数。第三个问题根本就没有解决,也没法解决,这个是设计上本身就有问题了。
本身使用浮点数作为控制量(比如循环终结值)就会产生很大风险,在出现很小的浮点偏差的时候可能会产生很大的运行结果差别。
1.0和0.1有啥差别呢?看下面的代码:
header('Content-Type: text/plain;charset=utf-8');
$sum = 0.0;
for($i = 0; $i < 100000000; $i++)
{
$sum += 0.1;
}
echo ($sum - 10000000.0)."rn";
$sum = 0.0;
for($i = 0; $i < 100000000; $i++)
{
$sum += 1.0;
}
echo ($sum - 100000000.0)."rn";
输出:
-0.0188705492765
0
误差:0
这就是0.1和1.0的差别
既然大家都已经明确是精度导致的问题,为何不降一位小数做一下 trick 呢?
ps: 根据下面一段关于精度的代码,可知在一次循环中,float 元素的计算会导致的最大误差不会超过 10e-8。于是我们便可以将 10e-8 这个数值作为一小小的增幅,防止出现被丢弃的 2.4 这类问题。
<?php
function dotrand($startnum,$endnum)
{
// 由原来的 0.01 改为 10e-8,避免 灵剑 说明的问题。
$endnum += pow(10, -8);
for ($i=$startnum; $i<$endnum; $i=$i+0.1)
{
$re[]=$i;
}
return $re;
}
$c=dotrand(2.0, 2.4);
print_r($c);
循环体里面的分度值是 0.1,那就再没有循环之前添加一个 10e-8 的增量,然后就可以直接使用大于号作为逻辑判断了。
至于黄远威同学的疑问,个人感觉还是值得考虑的,当然,php 默认只能等待 30s 的计算延迟,所以我写了个测试程序:
function getMilliTime()
{
list($usec, $sec) = explode(' ', microtime());
return ((float)$usec + (float)$sec);
}
$delayed = getMilliTime();
$sum = 0.0;
for ($i = 0; $i < pow(10, 7); $i++)
{
$sum += pow(10, -7); // 这里为了测试 从 10e-7 到 10e-10 都可以
}
echo "sum = ".$sum.", ";
if($sum == 1)
{
echo "equal 1 in expr.";
}
else
{
echo "not equal 1 in expr.";
}
echo "<br>delayed: ".(getMilliTime() - $delayed)."s<br>";
通过我的pc来看,pow(10, 7) 次循环就已经超过 10s 了,如果有兴趣可以尝试一下 pow(10, 8),一般都是要有错误提示了,执行这样多次的循环差不多要超过 30s 的限制了。
而回头看看精度造成的误差呢:几次运行下来,大约在正负 0.00000000060 以内。
当然,不论循环几次,只要有float类型的运算,等值的判断都不会成立的。
所以,个人认为,使用分度值再降一位作为增量的做法是可行的,因为它确实可是消除判等浮点数值的问题。
祝好,
斑驳敬上
<?php
function dotrand($startnum,$endnum){
$i=$startnum;
while(1){
if(bccomp($i,$endnum,1)>0) break;
$re[]=$i;
$i=$i+0.1;
}
return $re;
}
$c=dotrand(2.0,2.4);
print_r($c);
?>
在php里慎用一般的比较运算符比较浮点数!
一般比较float数用int bccomp(string left operand, string right operand, int [scale])
函数比较二个高精确度的数字。输入二个字符串,若二个字符串一样大则返回 0;若左边的数字字符串 (left operand) 比右边 (right operand) 的大则返回 +1;若左边的数字字符串比右边的小则返回 -1。scale 是一个可有可无的选项,表示返回值的小数点后所需的位数。
恐怕这是浮点数的精度问题。
由于许多十进制小数转换为二进制小数后,结果是无限循环或不循环小数,但cpu二进制位数是有限的,无法用一个位数有限的内存表示位数无限的值。
只好用一些方法例如截断去处理,结果就会有一点点误差了。
而对比起一次加0.4来说,加4次0.1的误差较大,恰好就比2.4要大了
那看来是浮点数精度的问题了。
function dotrand($startnum,$endnum){
//for ($i=$startnum;$i<=$endnum;$i=$i+0.1){
for ($i=$startnum;$i<$endnum;$i=$i+0.1){
print_r($i);echo "<br>";
print_r($startnum);echo "<br>";
print_r($endnum);echo "<br>";
$re[]=$i;
}
return $re;
}
$c = dotrand(2.0,2.4);
浮点数精度问题 浮点数计算啊。。。。。。
给个以前写过的例子 只是部分代码
不做解释 给个可以的出正确结果的代码 程序是用来解决问题的
// 获取浮点数精度
function get_scale($strfloat)
{
if (strpos($strfloat, '.') === false) return 0;
list(, $fscale) = explode('.', $strfloat);
return strlen($fscale);
}
// 相加两个浮点数 精度是两个浮点数里最大精度
function add_float($str_f1, $str_f2)
{
return bcadd($str_f1, $str_f2, max(get_scale($str_f1), get_scale($str_f2)));
}
function dotrand($startnum,$endnum){
for ($i=$startnum;$i<=$endnum;$i=add_float($i+0.1)){
$re[]=$i;
}
return $re;
}
$c=dotrand(2.0,2.4);
print_r($c);
结果是:
Array
(
[0] => 2
[1] => 2.1
[2] => 2.2
[3] => 2.3
[4] => 2.4
)