Skip to content

Commit 9897e80

Browse files
新亮新亮
authored andcommitted
add PHP
1 parent fb626b2 commit 9897e80

24 files changed

+2426
-2
lines changed

01-PHP 浮点数高精度运算.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
## 概述
2+
3+
记录下,工作中遇到的坑 ...
4+
5+
关于 PHP 浮点数运算,特别是金融行业、电子商务订单管理、数据报表等相关业务,利用浮点数进行加减乘除时,稍不留神运算结果就会出现偏差,轻则损失几十万,重则会有信誉损失,甚至吃上官司,我们一定要引起高度重视!
6+
7+
## 浮点数运算的“锅”
8+
9+
```
10+
//加
11+
$a = 0.1;
12+
$b = 0.7;
13+
$c = intval(($a + $b) * 10);
14+
echo $c."<br>";
15+
//输出:7
16+
17+
//减
18+
$a = 100;
19+
$b = 99.98;
20+
$c = $a - $b;
21+
echo $c."<br>";
22+
//输出:0.019999999999996
23+
24+
//乘
25+
$a = 0.58;
26+
$b = 100;
27+
$c = intval($a * $b);
28+
echo $c."<br>";
29+
//输出:57
30+
31+
//除
32+
$a = 0.7;
33+
$b = 0.1;
34+
$c = intval($a / $b);
35+
echo $c."<br>";
36+
//输出:6
37+
```
38+
39+
上面的结果,显然不是我们想要的!
40+
41+
PHP 官方手册解释如下:
42+
43+
> 浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用**任意精度数学函数** 或者 **gmp 函数**
44+
45+
这里的关键在于,浮点数的小数用二进制的表示,过程如下:
46+
47+
- 将小数乘以2,取整数部分表示第一位;
48+
- 将小数部分乘以2,取整数部分表示第二位;
49+
- 再将小数部分乘以2,取整数部分表示第三位;
50+
- ... 依次类推,直到小数部分为0;
51+
52+
例:0.58
53+
54+
- 0.58 * 2 = 1.16 ---> 1
55+
- 0.16 * 2 = 0.32 ---> 0
56+
- 0.32 * 2 = 0.64 ---> 0
57+
- 0.64 * 2 = 1.28 ---> 1
58+
- 0.28 * 2 = 0.56 ---> 0
59+
- 0.56 * 2 = 1.12 ---> 1
60+
- 0.12 * 2 = 0.24 ---> 0
61+
- 0.24 * 2 = 0.48 ---> 0
62+
- 0.48 * 2 = 0.96 ---> 0
63+
- 0.96 * 2 = 1.92 ---> 1
64+
- ...
65+
66+
我们会得到一个无限循环的二进制小数:
67+
68+
0.1001010001...
69+
70+
小数部分出现循环,有限的二进制位无法准确的表示一个小数,这也就是小数运算出现误差的原因。
71+
72+
接下来给大家介绍 **任意精度数学函数**
73+
74+
## 任意精度数学函数
75+
76+
对于任意精度的数学,PHP 提供了支持用字符串表示的任意大小和精度的数字的二进制计算。
77+
78+
BCMath:BC 是 Binary Calculator 的缩写。
79+
80+
官方手册:http://php.net/manual/zh/book.bc.php
81+
82+
大家在使用前,请先确认是否已安装 bcmath。
83+
84+
```
85+
//加
86+
$a = 0.1;
87+
$b = 0.7;
88+
$c = intval(bcadd($a, $b, 1) * 10);
89+
echo $c."<br>";
90+
//输出:8
91+
92+
//减
93+
$a = 100;
94+
$b = 99.98;
95+
$c = bcsub($a, $b, 2);
96+
echo $c."<br>";
97+
//输出:0.02
98+
99+
//乘
100+
$a = 0.58;
101+
$b = 100;
102+
$c = intval(bcmul($a, $b));
103+
echo $c."<br>";
104+
//输出:58
105+
106+
//除
107+
$a = 0.7;
108+
$b = 0.1;
109+
$c = intval(bcdiv($a, $b));
110+
echo $c."<br>";
111+
//输出:7
112+
```
113+
114+
除了加减乘除,bcmath 还提供了以下方法:
115+
116+
- bccomp 比较两个任意精度的数字
117+
- bcmod 对一个任意精度数字取模
118+
- bcpow 任意精度数字的乘方
119+
- bcpowmod 高精度数字乘方求模
120+
- bcscale 设置所有bc数学函数的默认小数点保留位数
121+
- bcsqrt 任意精度数字的二次方根
122+
123+
## 常用数值处理方案
124+
125+
#### 舍去法取整(向下取整)
126+
127+
```
128+
echo floor(5.1);
129+
//输出:5
130+
131+
echo floor(8.8);
132+
//输出:8
133+
```
134+
135+
#### 进一法取整(向上取整)
136+
137+
```
138+
echo ceil(5.1);
139+
//输出:6
140+
141+
echo ceil(8.8);
142+
//输出:9
143+
```
144+
145+
#### 普通四舍五入法
146+
147+
```
148+
echo round(5.1);
149+
//输出:5
150+
151+
echo round(8.8);
152+
//输出:9
153+
154+
//保留两位小数并且进行四舍五入
155+
echo round(5.123, 2);
156+
//输出:5.12
157+
158+
echo round(8.888, 2);
159+
//输出:8.89
160+
161+
//保留两位小数并且不进行四舍五入
162+
echo substr(round(5.12345, 3), 0, -1);
163+
//输出:5.12
164+
165+
echo substr(round(8.88888, 3), 0, -1);
166+
//输出:8.88
167+
```
168+
169+
#### 银行家舍入法
170+
171+
四舍六入五考虑,五后非空就进一,五后为空看奇偶,五前为偶应舍去,五前为奇要进一。
172+
173+
保留两位小数,例:
174+
175+
- 1.2849 = 1.28 -> 四舍
176+
- 1.2866 = 1.29 -> 六入
177+
- 1.2851 = 1.29 -> 五后非空就进一
178+
- 1.2850 = 1.28 -> 五后为空看奇偶,五前为偶应舍去
179+
- 1.2750 = 1.28 -> 五后为空看奇偶,五前为奇要进一
180+
181+
实现代码如下:
182+
183+
```
184+
echo round(1.2849, 2, PHP_ROUND_HALF_EVEN);
185+
//输出:1.28
186+
187+
echo round(1.2866, 2, PHP_ROUND_HALF_EVEN);
188+
//输出:1.29
189+
190+
echo round(1.2851, 2, PHP_ROUND_HALF_EVEN);
191+
//输出:1.29
192+
193+
echo round(1.2850, 2, PHP_ROUND_HALF_EVEN);
194+
//输出:1.28
195+
196+
echo round(1.2750, 2, PHP_ROUND_HALF_EVEN);
197+
//输出:1.28
198+
```
199+
200+
更多 round 使用说明,请查阅官方手册:
201+
202+
http://php.net/manual/zh/function.round.php
203+
204+
#### 数值格式化(千位分组)
205+
206+
应用于金额的展示,比如我们经常会看的银行卡余额。
207+
208+
```
209+
echo number_format('10000.98', 2, '.', ',');
210+
//输出:10,000.98
211+
212+
echo number_format('340888999', 2, '.', ',');
213+
//输出:340,888,999.00
214+
```
215+
216+
## 扩展
217+
218+
#### MySQL 浮点型字段
219+
220+
在 MySQL 中,创建表字段时也有浮点数类型。
221+
222+
浮点数类型包括单精度浮点数(float)和双精度浮点数(double)。
223+
224+
**同理,不建议使用浮点数类型!!!**
225+
226+
浮点数存在误差,当我们使用精度敏感的数据时,应该使用定点数(decimal)进行存储。
227+
228+
## 小结
229+
230+
通过浮点数精度的问题,了解到浮点数的小数用二进制的表示。
231+
232+
分享了用 **PHP 任意精度数学函数**,来进行高精度运算。
233+
234+
同时分享了常用数值处理方案,比如舍去法、进一法、四舍五入法、银行家舍入法、数值格式化 等。
235+
236+
最后,通过 PHP 的 float 联想到 MySQL 的 float。
237+
238+
以后,在使用浮点数运算的时候,一定要慎之又慎,细节决定成败。

0 commit comments

Comments
 (0)