首页 > 编程技术 > php

php中浮点数运算常见问题

发布时间:2016-11-25 15:30

php中浮点数运算常见问题有哪些呢,今天我们就一起来看看这些关于php中浮点数运算常见问题总结吧,具体的如下文介绍.

本文实例分析了php中让人头疼的浮点数运算。分享给大家供大家参考,具体如下:
在做电商的时候,计算价格是免不了的,然后发现了php的一个坑,口算应该正确的值,php运算出来会跟你不一样
请看下面的代码:

$price=69.1;
$count=100;
$total=$price*$count-6910;
echo $total;

你猜一下变量$total的值是多少,运行一下这个代码输出:-9.09494701773E-13
怎么解决这个问题呢?
使用round函数

代码修改成:


$price=69.1;
$count=100;
$total=round($price*$count)-6910;
echo $total;

浮点数计算结果比较
 一则浮点数计算例子如下:
 
 $a = 0.2+0.7;
 $b = 0.9;
 var_dump($a == $b);
 

打印出的结果是:bool(false)。也就是说在这里 0.2+0.7 的计算结果与 0.9 并不相等,这显然是有违我们的常识的。
 
对此问题,PHP官方手册曾又说明:显然简单的十进制分数如 0.2 不能在不丢失一点点精度的情况下转换为内部二进制的格式。这和一个事实有关,那就是不可能精确的用有限位数表达某些十进制分数。例如,十进制的 1/3 变成了 0.3333333...。
 
我们将上面的变量用双精度格式打印出来:


 $a = 0.2+0.7;
 $b = 0.9;
 printf("%0.20f", $a);
 echo '<br />';
 printf("%0.20f", $b);
 

输出结果如下:
 
 0.89999999999999991118
 0.90000000000000002220
 

显然在这里,实际上作为浮点型数据,其精度已经损失了一部分,达不到完全精确。所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。需要说明的是,这不是PHP的问题,而是计算机内部处理浮点数的问题!在 C、JAVA 等语言中也会遇到同样的问题。
 
所以要比较两个浮点数,需要将其控制在我们需要的精度范围内再行比较,因此使用 bcadd() 函数来对浮点数想加并进行精度转换(为字符串):
 

 var_dump(bcadd(0.2,0.7,1) == 0.9); // 输出:bool(true)


浮点数取整
 
在《PHP 取整函数 ceil 与 floor》一文中,曾有例子:
 

 <?php
 echo ceil(2.1/0.7);    // 输出:4
 ?>
 

经过上面对浮点数计算的探讨,知道这是浮点数计算结果不完全精确造成的:
 

 <?php
 printf("%0.20f", (2.1/0.7));    // 输出:3.00000000000000044409
 ?>
 
经过上面对浮点数计算的探讨,知道这是浮点数计算结果不完全精确造成的,因此使用 round() 函数处理一下即可:
 
 <?php
 echo ceil( round((2.1/0.7),1) );
 ?>
 


虽然 round() 函数是按照指定的精度进行四舍五入,但保留小数点后一位,对我们的取整结果是没影响的

文件锁可以防止文件读写时多人访问时出现共享问题或数据不准确的问题,下面我们来看一篇关于php文件锁类防止并发的例子,具体如下.

工作间隙写了个文件锁的类,用于解决并发的一些问题。

bool flock ( int handle, int operation [, int &wouldblock] );
flock() 操作的 handle 必须是一个已经打开的文件指针。operation 可以是以下值之一:

要取得共享锁定(读取程序),将 operation 设为 LOCK_SH(PHP 4.0.1 以前的版本设置为 1)
要取得独占锁定(写入程序),将 operation 设为 LOCK_EX(PHP 4.0.1 以前的版本中设置为 2)
要释放锁定(无论共享或独占),将 operation 设为 LOCK_UN(PHP 4.0.1 以前的版本中设置为 3)
如果你不希望 flock() 在锁定时堵塞,则给 operation 加上 LOCK_NB(PHP 4.0.1 以前的版本中设置为 4)
建两个文件
(1) a.php

$file = "temp.txt";   
$fp = fopen($file , 'w');   
if(flock($fp , LOCK_EX)){   
     fwrite($fp , "abc\n");   
     sleep(10);   
     fwrite($fp , "123\n");   
    flock($fp , LOCK_UN);   
}   
fclose($fp);  
(2) b.php

$file = "temp.txt";   
$fp = fopen($file , 'r');   
echo fread($fp , 100);   
fclose($fp);  
运行 a.php 后,马上运行 b.php ,可以看到输出:
abc
等 a.php 运行完后运行 b.php ,可以看到输出:
abc
123
显然,当 a.php 写文件时数据太大,导致时间比较长时,这时 b.php 读取数据不完整

修改 b.php 为:

$file = "temp.txt";   
$fp = fopen($file , 'r');   
if(flock($fp , LOCK_EX)){   
    echo fread($fp , 100);   
    flock($fp , LOCK_UN);   
} else{   
    echo "Lock file failed...\n";   
}   
fclose($fp);  
运行 a.php 后,马上运行 b.php ,可以发现 b.php 会等到 a.php 运行完成后(即 10 秒后)才显示:
abc
123
读取数据完整,但时间过长,他要等待写锁释放。

修改 b.php 为:

$file = "temp.txt";   
$fp = fopen($file , 'r');   
if(flock($fp , LOCK_SH | LOCK_NB)){   
    echo fread($fp , 100);   
    flock($fp , LOCK_UN);   
} else{   
    echo "Lock file failed...\n";   
}   
fclose($fp);  
运行 a.php 后,马上运行 b.php ,可以看到输出:
Lock file failed…
证明可以返回锁文件失败状态,而不是向上面一样要等很久。

结论:
建议作文件缓存时,选好相关的锁,不然可能导致读取数据不完整,或重复写入数据。
file_get_contents 好像选择不了锁,不知道他默认用的什么锁,反正和不锁得到的输出一样,是不完整的数据。
我是要做文件缓存,所以只需要知道是否有写锁存在即可,有的话就查数据库就可以了。
测试环境:Linux(Ubuntu 6) , PHP 5.1.2 , Apache 2

再转:

文件锁有两种:共享锁和排他锁,也就是读锁(LOCK_SH)和写锁(LOCK_EX)
文件的锁一般这么使用:
$fp = fopen("filename", "a");  
flock($fp, LOCK_SH) or die("lock error")  
$str = fread($fp, 1024);  
flock($fp, LOCK_UN);  
fclose($fp); 

注意fwrite之后,文件立即就被更新了,而不是等fwrite然后fclose之后文件才会更新,这个可以通过在fwrite之后fclose之前读取这个文件进行检查

但是什么时候使用lock_ex什么时候使用lock_sh呢?

读的时候:
如果不想出现dirty数据,那么最好使用lock_sh共享锁。可以考虑以下三种情况:
1. 如果读的时候没有加共享锁,那么其他程序要写的话(不管这个写是加锁还是不加锁)都会立即写成功。如果正好读了一半,然后被其他程序给写了,那么读的后一半就有可能跟前一半对不上(前一半是修改前的,后一半是修改后的)
2. 如果读的时候加上了共享锁(因为只是读,没有必要使用排他锁),这个时候,其他程序开始写,这个写程序没有使用锁,那么写程序会直接修改这个文件,也会导致前面一样的问题
3. 最理想的情况是,读的时候加锁(lock_sh),写的时候也进行加锁(lock_ex),这样写程序会等着读程序完成之后才进行操作,而不会出现贸然操作的情况

写的时候:
如果多个写程序不加锁同时对文件进行操作,那么最后的数据有可能一部分是a程序写的,一部分是b程序写的
如果写的时候加锁了,这个时候有其他的程序来读,那么他会读到什么东西呢?
1. 如果读程序没有申请共享锁,那么他会读到dirty的数据。比如写程序要写a,b,c三部分,写完a,这时候读读到的是a,继续写b,这时候读读到的是ab,然后写c,这时候读到的是abc.
2. 如果读程序在之前申请了共享锁,那么读程序会等写程序将abc写完并释放锁之后才进行读。


<?php
/**
 * 用于解决PHP在并发时候的锁控制,不同的锁之间并行执行,类似mysql innodb的行级锁
 */
class FileLock{
    //文件锁存放路径
    private $path='';
    //文件句柄
    private $fp='';
    //锁文件
    private $lockFile='';
    /**
     * 构造函数
     * @param string $path 锁的存放目录
     * @param string $name 锁 KEY
     */
    public function __construct($name,$path=''){
  if(empty($path)) $this->path=dirname(__FILE__).'/';
  else $this->path=$path;
        $this->lockFile=$this->path.md5($name).'.lock';
    }
 
    /**
     * 加锁
     */
    public function lock(){
  $this->fp=fopen($this->lockFile,'a+');
  if($this->fp===false){
   return false;
  }
  return flock($this->fp,LOCK_EX);//获取独占锁
    }
    
    /**
     * 解锁
     */
    public function unlock(){
  if($this->fp!==false){
   @flock($this->fp,LOCK_UN);
   clearstatcache();
  }
  @fclose($this->fp);
  @unlink($this->lockFile);
    }
}

下面我们来看一篇关于php apidoc生成接口文档示例,希望这篇文章能够给各位带来帮助,具体的步骤细节如下文介绍.

api 生成接口文档示例:

{

  "name": "9月份赠险领取分享活动微信接口",

  "version": "1.0.0",

  "description": "9月份赠险领取分享活动微信接口文档 (t=1) ",

  "sampleUrl" : "http://www.phpernote.com/"

}

另存文件名为:apidoc.json

apidoc -i 要生成文档的项目目录 -o 接口文件的生成目录

接口代码文件代码如下:

<?php
/**
 * @api {post} /news/lists 新闻信息列表
 * @apiVersion 1.0.0
 * @apiName lists
 * @apiGroup User
 * @apiPermission 登录用户
 *
 * @apiDescription 用户登录后进入该页面,将显示新闻信息列表
 *
 * @apiSampleRequest /v1/news/lists
 *
 * @apiSuccess (返回值) {string} id 主键ID
 * @apiSuccess (返回值) {string} name 客户姓名
 * @apiSuccess (返回值) {string} user_head_img 客户头像
 * @apiSuccess (返回值) {integer} sex 性别:0-未设置,1-男,2-女
 * @apiSuccess (返回值) {string} demand 客户需求
 *
 * @apiSuccessExample {json} 成功示例:
 * {"code": 1,"msg": "","data": {"id": "57b3cdb46b787","name": "余浩苗","user_head_img": "userHead/2016-08-18/1034587522576.jpg","sex": "1","demand": "本人想买保险,请速联系!"}}
 *
 * @apiErrorExample (json) 错误示例:
 *     {"code":-1,"msg":"密码错误","data":{}}
 */

///////////////////////////////////////

/**
 * @api {post} /news/rob 抢客户
 * @apiVersion 1.0.0
 * @apiName rob
 * @apiGroup User
 * @apiPermission 登录用户
 *
 * @apiDescription 抢客户
 *
 * @apiParam {string} id 主键ID
 *
 * @apiSampleRequest /v1/news/rob
 *
 * @apiSuccessExample {json} 成功示例:
 * {"code":1,"msg":"","data":{}}
 *
 * @apiErrorExample (json) 错误示例:
 *     {"code":-1,"msg":"错误信息","data":{}}
 */

///////////////////////////////////////
更多关于apidoc的用法请参考:http://apidocjs.com/

下面我们来看一篇关于file_get_contents()获取https出现这个错误Unable to find the wrapper “https”问题的解决办法.

file_get_contents()获取https出现这个错误Unable to find the wrapper “https” – did

解决办法一,如果你是用的服务器,可以参考这个办法,修改php配置文件(win主机),来支持https

在php.ini中找到并修改

    extension=php_openssl.dll
    allow_url_include = On

重启服务就可以了,如果你的是linux服务器,linux下的PHP,就必须安装openssl模块,安装好了以后就可以访了。

解决办法二,如果你用的不是服务器,你用的主机,你没法更改php的配置,你可以通过使用curl函数来替代file_get_contents函数,当然你的主机必须支持curl函数。

<?php

function getSslPage($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
echo getSslPage($_GET['url']);
?>

检查主机是否支持php的file_get_contents函数

讲一下代码存储为php文件,在空间运行,即可得出结果。

<?php
echo ‘Curl: ‘, function_exists(‘curl_version’) ? ‘Enabled’ : ‘Disabled’ . ‘<br />
file_get_contents: ‘, file_get_contents(__FILE__) ? ‘Enabled’ : ‘Disabled’;
?>
 

标签:[!--infotagslink--]

您可能感兴趣的文章: