python 数字的四舍五入
问题
你想对浮点数执行指定精度的舍入运算。
解决方案
对于简单的舍入运算,使用内置的 round(value, ndigits) 函数即可。比如:
当一个值刚好在两个边界的中间的时候, round 函数返回离它最近的偶数。 也就是说,对1.5或者2.5的舍入运算都会得到2。
传给 round() 函数的 ndigits 参数可以是负数,这种情况下, 舍入运算会作用在十位、百位、千位等上面。比如:
讨论
不要将舍入和格式化输出搞混淆了。 如果你的目的只是简单的输出一定宽度的数,你不需要使用 round() 函数。 而仅仅只需要在格式化的时候指定精度即可。比如:
同样,不要试着去舍入浮点值来”修正”表面上看起来正确的问题。比如,你可能倾向于这样做:
对于大多数使用到浮点的程序,没有必要也不推荐这样做。 尽管在计算的时候会有一点点小的误差,但是这些小的误差是能被理解与容忍的。 如果不能允许这样的小误差(比如涉及到金融领域),那么就得考虑使用 decimal 模块了,下一节我们会详细讨论。
3.2 执行精确的浮点数运算
问题
你需要对浮点数执行精确的计算操作,并且不希望有任何小误差的出现。
解决方案
浮点数的一个普遍问题是它们并不能精确的表示十进制数。 并且,即使是最简单的数学运算也会产生小的误差,比如:
这些错误是由底层CPU和IEEE 754标准通过自己的浮点单位去执行算术时的特征。 由于Python的浮点数据类型使用底层表示存储数据,因此你没办法去避免这样的误差。
如果你想更加精确(并能容忍一定的性能损耗),你可以使用 decimal 模块:
初看起来,上面的代码好像有点奇怪,比如我们用字符串来表示数字。 然而, Decimal 对象会像普通浮点数一样的工作(支持所有的常用数学运算)。 如果你打印它们或者在字符串格式化函数中使用它们,看起来跟普通数字没什么两样。
decimal 模块的一个主要特征是允许你控制计算的每一方面,包括数字位数和四舍五入运算。 为了这样做,你先得创建一个本地上下文并更改它的设置,比如:
讨论
decimal 模块实现了IBM的”通用小数运算规范”。不用说,有很多的配置选项这本书没有提到。
Python新手会倾向于使用 decimal 模块来处理浮点数的精确运算。 然而,先理解你的应用程序目的是非常重要的。 如果你是在做科学计算或工程领域的计算、电脑绘图,或者是科学领域的大多数运算, 那么使用普通的浮点类型是比较普遍的做法。 其中一个原因是,在真实世界中很少会要求精确到普通浮点数能提供的17位精度。 因此,计算过程中的那么一点点的误差是被允许的。 第二点就是,原生的浮点数计算要快的多-有时候你在执行大量运算的时候速度也是非常重要的。
即便如此,你却不能完全忽略误差。数学家花了大量时间去研究各类算法,有些处理误差会比其他方法更好。 你也得注意下减法删除以及大数和小数的加分运算所带来的影响。比如
上面的错误可以利用 math.fsum() 所提供的更精确计算能力来解决:
然而,对于其他的算法,你应该仔细研究它并理解它的误差产生来源。
总的来说, decimal 模块主要用在涉及到金融的领域。 在这类程序中,哪怕是一点小小的误差在计算过程中蔓延都是不允许的。 因此, decimal 模块为解决这类问题提供了方法。 当Python和数据库打交道的时候也通常会遇到 Decimal 对象,并且,通常也是在处理金融数据的时候。
3.3 数字的格式化输出
问题
你需要将数字格式化后输出,并控制数字的位数、对齐、千位分隔符和其他的细节。
解决方案
格式化输出单个数字的时候,可以使用内置的 format() 函数,比如:
如果你想使用指数记法,将f改成e或者E(取决于指数输出的大小写形式)。比如:
同时指定宽度和精度的一般形式是 ‘[<>^]?width[,]?(.digits)?’ , 其中 width 和 digits 为整数,?代表可选部分。 同样的格式也被用在字符串的 format() 方法中。比如:
讨论
数字格式化输出通常是比较简单的。上面演示的技术同时适用于浮点数和 decimal 模块中的 Decimal 数字对象。
当指定数字的位数后,结果值会根据 round() 函数同样的规则进行四舍五入后返回。比如:
包含千位符的格式化跟本地化没有关系。 如果你需要根据地区来显示千位符,你需要自己去调查下 locale 模块中的函数了。 你同样也可以使用字符串的 translate() 方法来交换千位符。比如:
在很多Python代码中会看到使用%来格式化数字的,比如:
这种格式化方法也是可行的,不过比更加先进的 format() 要差一点。 比如,在使用%操作符格式化数字的时候,一些特性(添加千位符)并不能被支持。
3.4 二八十六进制整数
问题
你需要转换或者输出使用二进制,八进制或十六进制表示的整数。
解决方案
为了将整数转换为二进制、八进制或十六进制的文本串, 可以分别使用 bin() , oct() 或 hex() 函数:
另外,如果你不想输出 0b , 0o 或者 0x 的前缀的话,可以使用 format() 函数。比如:
整数是有符号的,所以如果你在处理负数的话,输出结果会包含一个负号。比如:
如果你想产生一个无符号值,你需要增加一个指示最大位长度的值。比如为了显示32位的值,可以像下面这样写:
为了以不同的进制转换整数字符串,简单的使用带有进制的 int() 函数即可:
讨论
大多数情况下处理二进制、八进制和十六进制整数是很简单的。 只要记住这些转换属于整数和其对应的文本表示之间的转换即可。永远只有一种整数类型。
最后,使用八进制的程序员有一点需要注意下。 Python指定八进制数的语法跟其他语言稍有不同。比如,如果你像下面这样指定八进制,会出现语法错误:
3.5 字节到大整数的打包与解包
问题
你有一个字节字符串并想将它解压成一个整数。或者,你需要将一个大整数转换为一个字节字符串。
解决方案
假设你的程序需要处理一个拥有128位长的16个元素的字节字符串。比如:
data = b’x00x124Vx00xx90xabx00xcdxefx01x00#x004’
为了将bytes解析为整数,使用 int.from_bytes() 方法,并像下面这样指定字节顺序:
为了将一个大整数转换为一个字节字符串,使用 int.to_bytes() 方法,并像下面这样指定字节数和字节顺序:
讨论
大整数和字节字符串之间的转换操作并不常见。 然而,在一些应用领域有时候也会出现,比如密码学或者网络。 例如,IPv6网络地址使用一个128位的整数表示。 如果你要从一个数据记录中提取这样的值的时候,你就会面对这样的问题。
作为一种替代方案,你可能想使用6.11小节中所介绍的 struct 模块来解压字节。 这样也行得通,不过利用 struct 模块来解压对于整数的大小是有限制的。 因此,你可能想解压多个字节串并将结果合并为最终的结果,就像下面这样:
字节顺序规则(little或big)仅仅指定了构建整数时的字节的低位高位排列方式。 我们从下面精心构造的16进制数的表示中可以很容易的看出来:
如果你试着将一个整数打包为字节字符串,那么它就不合适了,你会得到一个错误。 如果需要的话,你可以使用 int.bit_length() 方法来决定需要多少字节位来存储这个值。