本文实例分析了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() 函数是按照指定的精度进行四舍五入,但保留小数点后一位,对我们的取整结果是没影响的
工作间隙写了个文件锁的类,用于解决并发的一些问题。
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);
}
}
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” – 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’;
?>