浮点算术:争议和限制
浮点数在计算机硬件中表示为以 2 为基数(二进制)的小数。
举例而言,十进制的小数
0.125=1/10 + 2/100 + 5/1000
同理二进制小数
0.001
0*1/2+0*1/4+1*1/8得十进制的0.125(二进制的小数转换为十进制主要是乘以2的负次方)
这两个小数具有相同的值,唯一真正的区别是第一个是以 10 为基数的小数表示法,第二个则是 2 为基数。
大多数的十进制小数都不能精确地表示为二进制小数。这导致在大多数情况下,你输入的十进制浮点数都只能近似地以二进制浮点数形式储存在计算机中。
用十进制来理解这个问题显得更加容易一些。考虑分数 1/3 。
我们可以得到它在十进制下的一个近似值 0.3
或者,更近似的,:0.33
或者,更近似的,:0.333
以此类推。结果是无论你写下多少的数字,它都永远不会等于 1/3 ,只是更加更加地接近 1/3 。
同样的道理,无论你使用多少位以 2 为基数的数码,十进制的 0.1 都无法精确地表示为一个以 2 为基数的小数。 在以 2 为基数的情况下, 1/10 是一个无限循环小数
0.0001100110011001100110011001100110011001100110011...
计算机只认识二进制,十进制之间的运算,需要将十进制转换为二进制在进行二进制之间的运算。
因为 Python 只会打印计算机中存储的二进制值的十进制近似值
>>>print(0.1+0.1+0.1)
0.30000000000000004
虽然病态的情况确实存在,但对于大多数正常的浮点运算使用来说,你只需简单地将最终显示的结果舍入为你期望的十进制数值即可得到你期望的结果。 str() 通常已足够,对于更精度的控制可参看 格式字符串语法 中 str.format() 方法的格式描述符。
对于需要精确十进制表示的使用场景,请尝试使用 decimal 模块,该模块实现了适合会计应用和高精度应用的十进制运算(数学上的四舍五入)
#新Decimal的重要性仅由输入的位数决定。 上下文精度和舍入仅在算术运算期间发挥作用。>>>print(decimal.Decimal('0.01')+decimal.Decimal('0.01')+decimal.Decimal('0.01')) 0.03 总是输入 decimal.Decimal('0.01')过于笨拙。 可将构造器简写为一个字母: >>>D = decimal.Decimal >>>D('0.01') + D('0.01') + D('0.01')
如何正确进行四舍五入
来测试一下,0.125 ,0.375 分别保留两位小数是多少:
print(decimal.Decimal('0.125').quantize(Decimal('.01'))) 0.12
print(decimal.Decimal('0.375').quantize(Decimal('.01'))) 0.38
来并未达到预期效果,
我们可以通过指定quantize() 中 rounding
参数来确定进位方式。如果没有指定 rounding
参数,那么默认使用上下文提供的进位方式。rounding=ROUND_HALF_EVEN
ROUND_HALF_EVEN
实际上就是 奇进偶舍
!如果要指定真正的四舍五入,那么我们需要在 quantize
中指定进位方式为 ROUND_HALF_UP
:
quantize(Decimal('.01'), rounding=decimal.ROUND_HALF_UP)
Python的官方中文文档 decimal --- 十进制定点和浮点运算 — Python 3.10.2 文档
关于使用round函数
round
(number[, ndigits])返回 number 舍入到小数点后 ndigits 位精度的值。 如果 ndigits 被省略或为
None
,则返回最接近输入值的整数。对于支持 round() 方法的内置类型,结果值会舍入至最接近的 10 的负 ndigits 次幂的倍数;如果与两个倍数同样接近,则选用偶数。因此,
round(0.5)
和round(-0.5)
均得出0
而round(1.5)
则为2
。ndigits 可为任意整数值(正数、零或负数)。如果省略了 ndigits 或为None
,则返回值将为整数。否则返回值与 number 的类型相同。
round
对小数的精确度采用了 四舍六入五成双
的方式。( 奇进偶舍
的处理方法)
其具体要求举例如下(以保留两位小数为例):一个小数 a.bcd
-
如果
d
小于5,直接舍去,则舍去。例如:5.214保留两位小数为5.21。 -
如果
d
大于5,直接进位。例如5.216保留两位小数为5.22。 -
如果
d
等于5:而且d后面不再有数,要根据应看尾数“5”的前一位C决定是舍去还是进入: 如果是奇数进入,如果是偶数舍去。例如5.215保留两位小数为5.22; 5.225保留两位小数为5.22。 -
如果
d
等于5:而且d后面仍有数。例如5.2254保留两位小数为5.23,也就是说如果5后面还有数据,则无论奇偶都要进入。
四舍五入(保留2位小数,只舍不入)
通过计算的途径,很难将最终结果截取2位,通过字符串,直接截取就可以了。
>>> a = 12.345
>>> str(a).split('.')[0] + '.' + str(a).split('.')[1][:2]
'12.34'
def get_float(f_str,n):
a, b, c = f_str.partition('.')
c = c[:n]
return ".".join([a, c])
if __name__ == '__main__':
print(get_float("12.345", 2))
#使用re模块
a = 12.345
re.findall(r"\d{1,}?\.\d{2}", str(a))
四舍五入(保留2位小数,只入不舍)
不根据小数位第三位进行四舍五入,只要后面有值就进行入
print(decimal.Decimal('0.370001').quantize(Decimal('.01'), rounding=decimal.ROUND_UP))
0.38
根据小数位第三位进行四舍五入
先截取成三位小数再进行 rounding=decimal.ROUND_UP
向上/下取整(ceil(a)函数与floor(a)函数)
import math
# 向上取整 3
import math
print("math.ceil(2.3) => ", math.ceil(2.3))
print("math.ceil(2.6) => ", math.ceil(2.6))
# 向下取整 2
print("math.floor(2.3) => ", math.floor(2.3))
print("math.floor(2.6) => ", math.floor(2.6))