Python技法:浮点数取整、格式化和NaN处理
1. 取整的三种方法
1.1 强转int类型
这种方法会直接对浮点数的小数部分进行截断(无论是正还是负)。
print(int(2.7)) # 2
print(int(-2.7)) # -2
1.2 采用math.ceil和math.floor
这种方法的取整规则如下图所示:
可以看到无论是正数还是负数,都遵循:ceil
往数轴正方向取整,floor
往数轴负方向取整。实例如下:
print(math.ceil(-1.27)) # -1
print(math.floor(-1.27)) # -2
print(math.ceil(1.27)) # 2
print(math.floor(1.27)) # 1
1.3 采用round
round原型为round(value, ndigits)
,可以将一个浮点数取整到固定的小数位。该函数对正数和负数都采取就近取整原则,而当某个值恰好等于两个整数间一半时,取整操作会取到离该值最近的那个偶数。像1.5和2.5这样的值都会取整到2。示例如下:
print(round(1.23, 0)) # 1.0
print(round(1.23, 1)) # 1.2
print(round(1.27, 1)) # 1.3
print(round(-1.27, 1)) # -1.3
print(round(1.25361, 3)) # 1.254
print(round(1.5, 0)) # 2.0
print(round(2.5, 0)) # 2.0
传递给round()
参数ndigits
可以是负数,这种情况下回相应取整到十位、百位、千位:
a = 1627731
print(round(a, -1)) # 1627730
print(round(a, -2)) # 1627700
print(round(a, -3)) # 1628000
2. 格式化浮点数输出
注意对值输出时别把取整和格式化操作混为一谈。如果只是将数值以固定位数输出,一般是用不着round()
的,只要在用format
格式化时指定所需要的精度即可(format()
格式化操作会根据round()
的规则进行取整,最终返回一个字符串类型)。
x = 1234.56789
s = format(x, "0.2f")
print(type(s), format(x, "0.2f")) # <class 'str'> 1234.57
除了取整到固定小数位,format()
还具有许多格式化功能,如格式化输出对齐,增加千分位分隔符等。实际上面的0.2f
就表示至少对齐到0个字符(相当于没有对齐操作),并保留两位小数。
小提示:
.2f
也表示至少对齐到0个字符(默认是0),并保留两位小数,
和0.2f
二者是等效的。
更多示例如下:
# 往右调整以对齐到10个字符
print(format(x, ">10.1f")) # 1234.6
# 往右调整以对齐到10个字符
print(format(x, "<10.1f")) # 1234.6
# 居中以对齐到10个字符
print(format(x, "^10.1f")) # 1234.6
# 增加千位分隔符
print(format(x, ",")) # 1,234.56789
# 增加千位分隔符并保存到1位小数
print(format(x, "0,.1f")) # 1,234.6
如果想使用科学计数法,只要把f
改成e
或E
即可:
print(format(x, "e")) # 1.234568e+03
print(format(x, "0.2E")) # 1.23E+03
此外,我们还可以利用字符串的translate()
方法交换不同的分隔符:
swap_separators = {ord("."):",", ord(","):"."}
print(format(x, ",").translate(swap_separators)) # 1.234,56789
最后,我们这里提一下,调用字符串的.format()
函数和单独调用format()
函数可以达到相同的效果,如:
print("value is {:0.3f}".format(x)) # value is 1.235
print("The value is {:0,.2f}".format(x)) # The value is 1,234.57
当然我们也可以使用%
操作符来对数值做格式化处理,如:
print("%.2f" % x)
print("%10.1f" % x)
print("%-10.1f" % x)
这种格式化操作虽然可行,但是比起更加现代化的format()
方法,这种方法就显得不是那么强大了。如用%
操作符来格式化数值时,有些功能就没法得到支持了(如添加千位分隔符)。
3. 执行精确的小数计算
我们在第一部分介绍了round()
函数,我们有可能会企图用浮点取整的方式来“修正”精度上的问题,如:
a = 2.1
b = 4.2
c = a + b
print(c) # 6.300000000000001
print(c==6.3) # False
print(round(c, 2)) # 6.3 企图这样修正精度(???)
对大部分浮点数应用程序(包括科学计算与机器学习)来说,一般都不必(或者所不推荐)这么做。虽然Python的浮点运算会引入一些小误差,但这些误差实际上是底层CPU的浮点运算单元和IEEE 754浮点算数标准的一种“特性”。由于Python的浮点数类型保存的数据采用的是原始保存形式,因此只要代码中用到了float
实例,那就无法避免这样的误差。
如果避免出现误差的行为非常重要(比如在金融应用中),那么可以考虑使用decimal
模块。事实上在用Python做数据库库接口时经常碰到Decimal
对象——当访问金融数据时尤其如此。我们通过使用Decimal
对象解决上述问题:
from decimal import Decimal
a = Decimal('4.2')
b = Decimal('2.1')
print(type(a + b), a + b) # <class 'decimal.Decimal'> 6.3
print((a + b) == Decimal('6.3')) # True
这么做看起来似乎有点怪异(将数字以字符串的形式来指定)。但是Decimal
对象能够以任何期望的方式来工作(支持所有常见的数学操作)。如果要将它们打印出来或者在字符串格式化函数中使用,它们看起来就和普通数字一样。它们也可以和普通int
、float
类型混合操作(最后会统一强转为Decimal
类型):
print(type(a + 1), a + 1) # <class 'decimal.Decimal'> 5.2
但是需要注意的是不要将其与普通float
类型直接进行比较:
print((a + b) == 6.3) # False
decimal
模块的强大之处在于在计算过程中灵活地控制数字的位数和四舍五入,如我们可以创建一个本地的上下文环境然后修改精度的设定,如:
from decimal import localcontext
a = Decimal("1.3")
b = Decimal("1.7")
print(a/b) # 0.7647058823529411764705882353
with localcontext() as ctx:
ctx.prec = 3
print(a/b) # 0.765
with localcontext() as ctx:
ctx.prec = 50
print(a/b) # 0.764705882352941176470588235294117647058823529
不过还是我们上面所说的,如果我们处理的是科学或工程类型的问题,那么更常见的做法是直接使用普通的float
浮点类型。首先,在真实世界中极少有东西需要计算到小数点后17位(float
提供17位的精度),因此在计算中引入的微小误差不足挂齿;其次,原生的float
浮点数运算性能要快许多——如果要执行大量计算,性能问题就显得很重要了。
在使用float
类型时,我们同样还需要对类似相减抵消(substraction cancellation)以及把大数和小数加载一起的情况多加小心:
nums = [1.23e+18, 1, -1.23e+18]
print(sum(nums)) # 0.0
使用Decimal
对象当然可以解决此问题。不过在不动用Decimal
对象的情况下,我们可以使用math.fsum()
以更精确的实现来解决:
import math
print(math.fsum(nums)) # 1.0
但对于其它复杂的数值算法,我们就需要研究算法本身,理解其误差传播(error propagation)了,这属于数值分析的研究范畴。在数值分析中数学家研究了大量数值算法,其中一些算法的误差处理能力优于其它算法,详情可以参见我的数值计算专栏《orion-orion:数值计算》,此处不再详述。
4. 无穷大、负无穷大和NaN的判断测试
在实际项目中我们需要对浮点数的无穷大、负无穷大或NaN(not a number)进行判断测试。在Python中没有特殊的语法来表示这些特殊的浮点值,但是它们可以通过float
来创建:
a = float("inf")
b = float("-inf")
c = float("nan")
print(a, b, c) # inf -inf nan
要检查是否出现了这些值,可以使用math.isinf()
和math.isnan()
函数:
print(math.isinf(a)) # True
print(math.isnan(c)) # True
这些特殊浮点数的详细信息可以参考IEEE 754规范。但是我们这里有几个棘手的问题需要搞清楚,尤其是设计比较操作和操作符时可能出现的问题。
无穷大值在数学计算中会进行传播,如:
a = float("inf")
print(a + 45) # inf
print(a * 10) # inf
print(10/a) # 0.0
但是,某些关于无穷大值特定的操作会导致未定义的行为并产生NaN
的结果,例如:
a = float("inf")
print(a/a) # nan
b = float("-inf")
print(a + b) # nan
NaN会通过所有的操作进行传播,且不会引发任何异常,如:
c = float("nan")
print(c + 23) # nan
print(c / 2) # nan
print(c + 2) # nan
有关NaN,一个微妙的特性是他们在做比较时从不会被判定为相等,如:
c = float("nan")
d = float("nan")
print(c == d) # False
print(c is d) # False
正因为如此,唯一安全检测NaN的方法是使用math.isnan()
。
参考
- [1] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2015.
- [2] https://stackoverflow.com/questions/15765289/what-is-the-difference-between-0-2lf-and-2lf-as-printf-placeholders
- [3] https://docs.python.org/3/
Python技法:浮点数取整、格式化和NaN处理的更多相关文章
- python 向下取整,向上取整,四舍五入
# python 向下取整 floor 向上取整ceil 四舍五入 round import math num=3.1415926 # 向上取整 print(math.ceil(num)) # 向下取 ...
- python中的取整
处理数据时,经常会遇到取整的问题,现总结如下 1,向下取整 int() >>>a = 3.1 >>>b = 3.7 >>>int(a) 3 > ...
- Python 之 向上取整、向下取整以及四舍五入函数
import math f = 11.2 print math.ceil(f) #向上取整 print math.floor(f) #向下取整 print round(f) #四舍五入 #这三个函数的 ...
- lua浮点数取整
向下取整 math.floor(num) 向上取整 math.ceil(num) 取整取余 math.modf(num) 测试 num = 12.4 print(math.floor(num)) 12 ...
- Python 向上取整的算法
一.初衷: 有时候我们分页展示数据的时候,需要计算页数.一般都是向上取整,例如counts=205 pageCouts=20 ,pages= 11 页. 一般的除法只是取整数部分,达不到要求. 二.方 ...
- numpy中的np.round()取整的功能和注意
numpy中的np.round()取整的功能和注意 功能 np.round() 是对浮点数取整的一个函数,一般的形式为 np.round(a, b),其中a为待取整的浮点数,b为保留的小数点的位数 注 ...
- Python取整及保留小数小结
1.int() 向下取整 内置函数 n = 3.75 print(int(n))>>> 3 n = 3.25 print(int(n))>>> 3 2.round ...
- python 取整的两种方法
问题简介: 要把一个浮点数(float)整数部分提取出来.比如把“2.1”变成“2”的这一过程:现在我们给这个过程起一个名字叫“取整”.那么它 在python中大致可以有两种写法 写法1)类型转换: ...
- Why Python's Integer Division Floors ---- python int(6/-132)时答案不一致,向下取整
leetcode150题中有一个步骤: int(6/-132) == 0 or ==-1? 在自己本地python3环境跑是int(6/-132) =0,但是提交的时候确实-1. 查找相关资料解惑: ...
随机推荐
- 网络协议之:socket协议详解之Datagram Socket
目录 简介 什么是Datagram Socket 使用socat来创建UDP服务 使用ss命令来监控Datagram Sockets 使用nc建立和UDP Socket的连接 总结 简介 上一篇文章我 ...
- 通过HTML5的getUserMedia实现拍照功能
参考HTML5Rocks的这篇文章实现的一个简单的例子. 思路如下: 1. 把冰箱门打开 2. 把大象放进冰箱里 3. 把冰箱门关上 好了不开玩笑了,其实思路是: 1. 通过getUserMedia调 ...
- 从零开始:微信小程序新手入门宝典《一》
为了方便大家了解并入门微信小程序,我将一些可能会需要的知识,列在这里,让大家方便的从零开始学习: 一:微信小程序的特点 张小龙:张小龙全面阐述小程序,推荐通读此文: 小程序是一种不需要下载.安装即可使 ...
- 体验javascript之美第五课 五分钟彻底明白 匿名函数自执行和闭包
通过文你将学到: 1.闭包是怎么回事儿? 2.闭包的原理和在jquery中的应用 3.从一到面试题彻底理解闭包和垃圾回收机制 4.闭包在jquery中的应用 概述 经常听到闭包这个词儿,或者匿名函数自 ...
- Wepy-小程序踩坑记
引言 用过原生开发的小程序也知道除了api 其他功能性的内容并不多对于需要做大型项目来说是比较难入手的,因此朋友推荐的wepy我就入坑鸟...这么一个跟vue的开发方式类似的框架,不过说起来跟vue类 ...
- Initialization failed for 'https://start.spring.io
本文精华(没空的小伙伴,直接看精华部分即可) 1.精华1: 开发/下载项目的时候考虑系统必备的版本兼容性 2.精华2: 通过火狐浏览器访问官网的旧版本,下载到任意需要的项目版本,然后通过项目导入ide ...
- uniapp最简单的上拉加载更多demo
data() { return { list:[],//数据列表 page: 1,//页数 } }, //请求一下数据(进入页面请求一次) onLoad() { this.getnewsList(th ...
- node的两种随起随用静态服务器搭建
一. anywhere Anywhere是一个随启随用的静态服务器,它可以随时随地将你的当前目录变成一个静态文件服务器的根目录. 1.确定电脑上安装了node.js 2.在当前所在项目文件夹下输入 ...
- python---变量、常量、注释、基本数据类型
变量 变量:将运算的中间结果暂存到内存中,以便后续程序调用. 变量的命令规则: 变量由字母.数字.下划线组合而成. 不可以数字开头,更不能全是数字. 不能是python的关键字. 不要用中文. 名字要 ...
- select下拉框获取下拉项值的问题
新发现: select option如果里面不写value值,默认提交<option></option>中间的值. 切记:真正提交的值写在value属性里面,option之间只 ...