HOWTO · Python
在 Python 中截断浮点数
本教程说明了如何在 Python 中截断浮点数
本页内容
Python 的 float 无法原地缩短:它是二进制浮点值,并不是带有固定小数位数的十进制字符串。若要删除指定小数位之后的数字,可以根据数字的十进制文本创建 Decimal,再使用 ROUND_DOWN 对其量化。若需要整数结果,可以使用 math.trunc() 或 int()。这些操作都向零截断;round() 和数值格式化的行为则不同。
区分截断与舍入
截断会删除指定位置之后的数字,并朝零的方向取值。舍入则根据舍入规则选取一个接近的值。Python 的 round(number, ndigits) 执行舍入,而不是截断。例如,round(3.146, 2) 会舍入为 3.15,而截断到两位小数会得到 3.14。Python 内置的 round() 对恰好处于中间的值采用取偶数规则,因此不应把它当作通用的十进制截断方法。
将浮点数截断到指定位数的小数
如果需要保留指定的小数位数,可以使用 Decimal。先根据字符串表示创建它,再调用 quantize() 并传入 ROUND_DOWN。在 decimal 模块中,ROUND_DOWN 表示向零舍入:它会朝零的方向删除数字,因此负数会向零靠近,而不是变得更负。传入 "3.14159" 这样的字符串可以保留预期的十进制输入;若直接从浮点数创建 Decimal,得到的则是该浮点数的二进制近似值。
运行以下示例:
本节中的代码以及显示的标签或输出仍为英文。
from decimal import Decimal, ROUND_DOWN
values = ("3.14159", "-3.14159", "0", "2.1")
places = 2
quantum = Decimal("1").scaleb(-places)
for text in values:
print(Decimal(text).quantize(quantum, rounding=ROUND_DOWN))
3.14
-3.14
0.00
2.10
前两个结果展示了正数和负数都会向零截断。量化还会保留指定的小数位数:零会变成 0.00,而 2.1 会变成 2.10。如果手头已有一个浮点数,并想以它最短的十进制显示形式作为输入,可以使用 Decimal(str(value));但这无法恢复浮点数创建之前已经丢失的十进制信息。对于货币或其他要求十进制精确的数值,最好从一开始就接收十进制文本,或用整数和字符串创建 Decimal。请参阅 decimal 模块文档。
向零截断为整数
如果需要整数而不是指定小数位数的结果,math.trunc() 会明确地向零截断并返回整数部分。将浮点数转换为整数时,内置函数 int() 也采用相同规则。例如,math.trunc(5.67) 的结果是 5,而 math.trunc(-3.45) 的结果是 -3。这两种方法都不会保留小数位;如果结果需要固定的小数位数,请使用 Decimal.quantize()。math.trunc() 也接受实现了 __trunc__() 的数值类型;该方法会提供截断所用的整数值。
为什么缩放浮点数可能产生意外结果
一种常见的简便做法是将浮点数乘以 10 的某个次方,把结果转换为整数,再除以相同的数。这对某些值有效,但二进制浮点数无法精确表示许多十进制小数。因此,乘法结果可能略低于或略高于预期的十进制值,导致 int(value * 100) / 100 删除了意料之外的数字。这个操作还会返回另一个浮点数,仍然受二进制表示的限制。处理对十进制精度敏感的数值时,不要依赖这种做法;应改用以文本为输入的 Decimal。如果计算本身就允许近似值,应继续使用浮点数并考虑其精度,不要把显示出来的数字当作精确值。
选择合适的方法
- 若要截断到指定位数的小数,请从十进制文本创建
Decimal,并使用quantize(..., rounding=ROUND_DOWN)。 - 若要删除小数部分并向零返回整数,请使用
math.trunc()或int()。 - 需要舍入而不是截断时,请使用
round()。 - 如果只需要控制显示文本,可以使用格式化。例如,f-string
f"{value:.2f}"会生成字符串,并对显示值进行舍入;它不会改变或截断底层浮点数。
根据需要的结果来选择方法:指定精度的十进制数、整数、经过舍入的值,或仅用于显示的格式化字符串。