【Python Cookbook】数字日期和时间(一)
数字日期和时间(一)
- 1.数字的四舍五入
- 2.执行精确的浮点数运算
- 3.数字的格式化输出
- 4.二、八、十六进制整数
1.数字的四舍五入
你想对浮点数执行指定精度的舍入运算。
对于简单的舍入运算,使用内置的 round(value, ndigits)
函数即可。比如:
>>> round(1.23, 1)
1.2
>>> round(1.27, 1)
1.3
>>> round(-1.27, 1)
-1.3
>>> round(1.25361,3)
1.254
>>>
当一个值刚好在两个边界的中间的时候, round
函数返回离它最近的偶数。也就是说,对
1.5
1.5
1.5 或者
2.5
2.5
2.5 的舍入运算都会得到
2
2
2。
传给 round()
函数的 ndigits
参数可以是负数,这种情况下,舍入运算会作用在十位、百位、千位等上面。比如:
>>> a = 1627731
>>> round(a, -1)
1627730
>>> round(a, -2)
1627700
>>> round(a, -3)
1628000
>>>
不要将舍入和格式化输出搞混淆了。如果你的目的只是简单的输出一定宽度的数,你不需要使用 round()
函数。而仅仅只需要在格式化的时候指定精度即可。比如:
>>> x = 1.23456
>>> format(x, '0.2f')
'1.23'
>>> format(x, '0.3f')
'1.235'
>>> 'value is {:0.3f}'.format(x)
'value is 1.235'
>>>
同样,不要试着去舍入浮点值来 “修正” 表面上看起来正确的问题。比如,你可能倾向于这样做:
>>> a = 2.1
>>> b = 4.2
>>> c = a + b
>>> c
6.300000000000001
>>> c = round(c, 2) # "Fix" result (???)
>>> c
6.3
>>>
对于大多数使用到浮点的程序,没有必要也不推荐这样做。尽管在计算的时候会有一点点小的误差,但是这些小的误差是能被理解与容忍的。如果不能允许这样的小误差(比如涉及到金融领域),那么就得考虑使用 decimal
模块了。
2.执行精确的浮点数运算
你需要对浮点数执行精确的计算操作,并且不希望有任何小误差的出现。
浮点数的一个普遍问题是它们并不能精确的表示十进制数。并且,即使是最简单的数学运算也会产生小的误差,比如:
>>> a = 4.2
>>> b = 2.1
>>> a + b
6.300000000000001
>>> (a + b) == 6.3
False
>>>
这些错误是由底层 CPU 和 IEEE 754 标准通过自己的浮点单位去执行算术时的特征。由于 Python 的浮点数据类型使用底层表示存储数据,因此你没办法去避免这样的误差。
如果你想更加精确(并能容忍一定的性能损耗),你可以使用 decimal
模块:
>>> from decimal import Decimal
>>> a = Decimal('4.2')
>>> b = Decimal('2.1')
>>> a + b
Decimal('6.3')
>>> print(a + b)
6.3
>>> (a + b) == Decimal('6.3')
True
初看起来,上面的代码好像有点奇怪,比如我们用字符串来表示数字。 然而,Decimal
对象会像普通浮点数一样的工作(支持所有的常用数学运算)。如果你打印它们或者在字符串格式化函数中使用它们,看起来跟普通数字没什么两样。
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.76470588235294117647058823529411764705882352941176
>>>
decimal
模块实现了 IBM 的 “通用小数运算规范”。不用说,有很多的配置选项这里没有提到。
Python 新手会倾向于使用 decimal
模块来处理浮点数的精确运算。然而,先理解你的应用程序目的是非常重要的。如果你是在做科学计算或工程领域的计算、电脑绘图,或者是科学领域的大多数运算,那么使用普通的浮点类型是比较普遍的做法。其中一个原因是,在真实世界中很少会要求精确到普通浮点数能提供的 17 位精度。因此,计算过程中的那么一点点的误差是被允许的。第二点就是,原生的浮点数计算要快的多(有时候你在执行大量运算的时候速度也是非常重要的)。
即便如此,你却不能完全忽略误差。数学家花了大量时间去研究各类算法,有些处理误差会比其他方法更好。你也得注意下减法删除以及大数和小数的加分运算所带来的影响。比如:
>>> nums = [1.23e+18, 1, -1.23e+18]
>>> sum(nums) # Notice how 1 disappears
0.0
>>>
上面的错误可以利用 math.fsum()
所提供的更精确计算能力来解决:
>>> import math
>>> math.fsum(nums)
1.0
>>>
然而,对于其他的算法,你应该仔细研究它并理解它的误差产生来源。
总的来说, decimal
模块主要用在涉及到金融的领域。在这类程序中,哪怕是一点小小的误差在计算过程中蔓延都是不允许的。 因此,
decimal
模块为解决这类问题提供了方法。当 Python 和数据库打交道的时候也通常会遇到 Decimal
对象,并且,通常也是在处理金融数据的时候。
3.数字的格式化输出
你需要将数字格式化后输出,并控制数字的位数、对齐、千位分隔符和其他的细节。
格式化输出单个数字的时候,可以使用内置的 format()
函数,比如:
>>> x = 1234.56789
>>> # Two decimal places of accuracy
>>> format(x, '0.2f')
'1234.57'
>>> # Right justified in 10 chars, one-digit accuracy
>>> format(x, '>10.1f')
' 1234.6'
>>> # Left justified
>>> format(x, '<10.1f')
'1234.6 '
>>> # Centered
>>> format(x, '^10.1f')
' 1234.6 '
>>> # Inclusion of thousands separator
>>> format(x, ',')
'1,234.56789'
>>> format(x, '0,.1f')
'1,234.6'
>>>
如果你想使用指数记法,将 f
改成 e
或者 E
(取决于指数输出的大小写形式)。比如:
>>> format(x, 'e')
'1.234568e+03'
>>> format(x, '0.2E')
'1.23E+03'
>>>
同时指定宽度和精度的一般形式是 '[<>^]?width[,]?(.digits)?'
, 其中 width
和 digits
为整数,?
代表可选部分。 同样的格式也被用在字符串的 format()
方法中。比如:
>>> 'The value is {:0,.2f}'.format(x)
'The value is 1,234.57'
>>>
数字格式化输出通常是比较简单的。上面演示的技术同时适用于浮点数和 decimal
模块中的 Decimal
数字对象。
当指定数字的位数后,结果值会根据 round()
函数同样的规则进行四舍五入后返回。比如:
>>> x
1234.56789
>>> format(x, '0.1f')
'1234.6'
>>> format(-x, '0.1f')
'-1234.6'
>>>
包含千位符的格式化跟本地化没有关系。如果你需要根据地区来显示千位符,你需要自己去调查下 locale
模块中的函数了。你同样也可以使用字符串的 translate()
方法来交换千位符。例如,从英语格式 1,234.56789
转换为欧洲格式 1.234,56789
。
>>> swap_separators = { ord('.'):',', ord(','):'.' } # 创建替换规则字典
>>> format(x, ',').translate(swap_separators)
'1.234,56789'
>>>
在很多 Python 代码中会看到使用 %
来格式化数字的,比如:
>>> '%0.2f' % x
'1234.57'
>>> '%10.1f' % x
' 1234.6'
>>> '%-10.1f' % x
'1234.6 '
>>>
这种格式化方法也是可行的,不过比更加先进的 format()
要差一点。比如,在使用 %
操作符格式化数字的时候,一些特性(添加千位符)并不能被支持。
4.二、八、十六进制整数
你需要转换或者输出使用二进制,八进制或十六进制表示的整数。
为了将整数转换为二进制、八进制或十六进制的文本串,可以分别使用 bin()
、oct()
或 hex()
函数:
>>> x = 1234
>>> bin(x)
'0b10011010010'
>>> oct(x)
'0o2322'
>>> hex(x)
'0x4d2'
>>>
另外,如果你不想输出 0b
,0o
或者 0x
的前缀的话,可以使用 format()
函数。比如:
>>> format(x, 'b')
'10011010010'
>>> format(x, 'o')
'2322'
>>> format(x, 'x')
'4d2'
>>>
整数是有符号的,所以如果你在处理负数的话,输出结果会包含一个负号。比如:
>>> x = -1234
>>> format(x, 'b')
'-10011010010'
>>> format(x, 'x')
'-4d2'
>>>
如果你想产生一个无符号值,你需要增加一个指示最大位长度的值。比如为了显示 32 位的值,可以像下面这样写:
>>> x = -1234
>>> format(2**32 + x, 'b')
'11111111111111111111101100101110'
>>> format(2**32 + x, 'x')
'fffffb2e'
>>>
🚀 上述代码演示了如何将负整数转换为无符号的二进制(
'b'
)和十六进制('x'
)字符串,利用了补码(Two's Complement
)的原理。
为了以不同的进制转换整数字符串,简单的使用带有进制的 int()
函数即可:
>>> int('4d2', 16)
1234
>>> int('10011010010', 2)
1234
>>>
大多数情况下处理二进制、八进制和十六进制整数是很简单的。只要记住这些转换属于整数和其对应的文本表示之间的转换即可。永远只有一种整数类型。
最后,使用八进制的程序员有一点需要注意下。Python 指定八进制数的语法跟其他语言稍有不同。比如,如果你像下面这样指定八进制,会出现语法错误:
>>> import os
>>> os.chmod('script.py', 0755)
File "<stdin>", line 1
os.chmod('script.py', 0755)
^
SyntaxError: invalid token
>>>
需确保八进制数的前缀是 0o
,就像下面这样:
>>> os.chmod('script.py', 0o755)
>>>