在Python中用 “round “和 “Decimal.quantize “对小数和整数进行取舍

商业

下面解释了如何在Python中通过四舍五入或四舍五入到偶数的方式对数字进行取舍。假设数字是浮点float或整数int类型。

  • 内置函数: round()
    • 将小数四舍五入到任何位数。
    • 将整数四舍五入到任何位数。
    • round()四舍五入到一个偶数,而不是一个普通的四舍五入。
  • 标准库decimal quantize()
    • Decimal创建一个对象
    • 小数四舍五入到任何位数,四舍五入到偶数
    • 整数四舍五入到任何位数和四舍五入到偶数的方法
  • 定义一个新的函数
    • 将小数四舍五入到任何位数。
    • 将整数四舍五入到任何位数
    • 注:对于负值

注意,如上所述,内置函数round不是一般的四舍五入,而是四舍五入到偶数。详见下文。

内置函数: round()

Round()是作为一个内置函数提供的。它可以在不导入任何模块的情况下使用。

第一个参数是原始数字,第二个参数是位数(四舍五入到多少位)。

将小数四舍五入到任何位数。

下面是一个对浮点浮子类型进行处理的例子。

如果省略第二个参数,它将被四舍五入为一个整数。类型也变成整数int类型。

f = 123.456

print(round(f))
# 123

print(type(round(f)))
# <class 'int'>

如果指定了第二个参数,它将返回一个浮点数的浮点类型。

如果指定一个正整数,则指定小数位;如果指定一个负整数,则指定整数位。-1四舍五入到最接近的十分之一,-2四舍五入到最接近的百分之一,0四舍五入到一个整数(第一位),但返回一个浮点数类型,与省略时不同。

print(round(f, 1))
# 123.5

print(round(f, 2))
# 123.46

print(round(f, -1))
# 120.0

print(round(f, -2))
# 100.0

print(round(f, 0))
# 123.0

print(type(round(f, 0)))
# <class 'float'>

将整数四舍五入到任何位数。

下面是一个处理整数int类型的例子。

如果省略了第二个参数,或者指定了0或正整数,则原样返回原值。如果指定了一个负的整数,它将被四舍五入到相应的整数位。在这两种情况下,都会返回一个整数的int类型。

i = 99518

print(round(i))
# 99518

print(round(i, 2))
# 99518

print(round(i, -1))
# 99520

print(round(i, -2))
# 99500

print(round(i, -3))
# 100000

round()四舍五入到一个偶数,而不是一个普通的四舍五入。

注意,在Python 3中用内置的round()函数进行四舍五入,是对偶数的舍入,而不是对一般的舍入。

正如官方文档中所写的,0.5被四舍五入为0,5被四舍五入为0,以此类推。

print('0.4 =>', round(0.4))
print('0.5 =>', round(0.5))
print('0.6 =>', round(0.6))
# 0.4 => 0
# 0.5 => 0
# 0.6 => 1

print('4 =>', round(4, -1))
print('5 =>', round(5, -1))
print('6 =>', round(6, -1))
# 4 => 0
# 5 => 0
# 6 => 10

四舍五入到偶数的定义如下。

如果分数小于0.5,四舍五入;如果分数大于0.5,四舍五入;如果分数正好是0.5,四舍五入到四舍五入之间的偶数。
Rounding – Wikipedia

0.5并不总是被截断。

print('0.5 =>', round(0.5))
print('1.5 =>', round(1.5))
print('2.5 =>', round(2.5))
print('3.5 =>', round(3.5))
print('4.5 =>', round(4.5))
# 0.5 => 0
# 1.5 => 2
# 2.5 => 2
# 3.5 => 4
# 4.5 => 4

在某些情况下,四舍五入的定义甚至不适用于小数点后两位的处理。

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

这是由于小数不能被精确地表示为浮点数,正如官方文件中所说的那样。

round()对浮点数的行为可能会让你吃惊:例如,round(2.675, 2)会给你2.67,而不是预期的2.68。这不是一个错误。:这是由于大多数小数不能用浮点数精确表示的结果。
round() — Built-in Functions — Python 3.10.2 Documentation

如果你想实现一般的四舍五入或将小数精确到偶数,你可以使用标准库小数量化(如下所述)或定义一个新的函数。

还要注意,Python 2中的round()不是四舍五入到偶数,而是四舍五入。

标准库中十进制的quantize()。

标准库的十进制模块可以用来处理精确的十进制浮点数。

使用十进制模块的quantize()方法,可以通过指定四舍五入模式对数字进行舍入。

quantize()方法的参数四舍五入的设定值分别具有以下含义。

  • ROUND_HALF_UP:一般四舍五入
  • ROUND_HALF_EVEN:四舍五入到双数

十进制模块是一个标准库,所以不需要额外安装,但导入是必要的。

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN

创建一个十进制的对象

Decimal()可以用来创建Decimal类型的对象。

如果你指定一个float类型作为参数,你可以看到数值实际上被当作什么来处理。

print(Decimal(0.05))
# 0.05000000000000000277555756156289135105907917022705078125

print(type(Decimal(0.05)))
# <class 'decimal.Decimal'>

正如例子中所示,0.05并不被视为正好是0.05。这就是上面描述的内置函数round()对包括例子中的0.05在内的十进制数值进行舍入的原因,与预期的数值不同。

由于0.5是二分之一(2的-1次方),它可以准确地用二进制符号表示。

print(Decimal(0.5))
# 0.5

如果你指定字符串类型str而不是float类型,它将被视为精确值的Decimal类型。

print(Decimal('0.05'))
# 0.05

小数四舍五入到任何位数,四舍五入到偶数

从一个十进制类型的对象中调用quantize()来舍弃数值。

quantize()的第一个参数是一个字符串,其位数与你想找到的位数相同,如'0.1'或'0.01'。

此外,参数ROUNDING指定了四舍五入模式;如果指定ROUND_HALF_UP,则使用一般四舍五入。

f = 123.456

print(Decimal(str(f)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 123

print(Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
# 123.5

print(Decimal(str(f)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 123.46

与内置函数round()不同,0.5被舍入为1。

print('0.4 =>', Decimal(str(0.4)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.5 =>', Decimal(str(0.5)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
print('0.6 =>', Decimal(str(0.6)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
# 0.4 => 0
# 0.5 => 1
# 0.6 => 1

如果参数rounding被设置为ROUND_HALF_EVEN,则会像内置函数round()那样对偶数进行舍入。

如上所述,如果指定一个浮点数的浮动类型作为Decimal()的参数,它将被视为一个Decimal对象,其值等于浮动类型的实际值,所以使用quantize()方法的结果会与预期的不同,就像内置函数round()一样。

print('0.05 =>', round(0.05, 1))
print('0.15 =>', round(0.15, 1))
print('0.25 =>', round(0.25, 1))
print('0.35 =>', round(0.35, 1))
print('0.45 =>', round(0.45, 1))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

print('0.05 =>', Decimal(0.05).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(0.15).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(0.25).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(0.35).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(0.45).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.1
# 0.15 => 0.1
# 0.25 => 0.2
# 0.35 => 0.3
# 0.45 => 0.5

如果Decimal()的参数被指定为str类型的字符串,那么它将被视为一个恰好是该值的Decimal对象,所以结果与预期一致。

print('0.05 =>', Decimal(str(0.05)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.15 =>', Decimal(str(0.15)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.25 =>', Decimal(str(0.25)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.35 =>', Decimal(str(0.35)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
print('0.45 =>', Decimal(str(0.45)).quantize(Decimal('0.1'), rounding=ROUND_HALF_EVEN))
# 0.05 => 0.0
# 0.15 => 0.2
# 0.25 => 0.2
# 0.35 => 0.4
# 0.45 => 0.4

由于0.5可以由float类型正确处理,所以当四舍五入到整数时,指定float类型作为Decimal()的参数是没有问题的,但是当四舍五入到小数位时,指定字符串str类型会比较安全。

例如,2.675在float类型中实际上是2.67499….。因此,如果你想四舍五入到小数点后两位,你必须给Decimal()指定一个字符串,否则无论你四舍五入到最接近的整数(ROUND_HALF_UP)还是偶数(ROUND_HALF_EVEN),结果都会与预期结果不同。

print(Decimal(2.675))
# 2.67499999999999982236431605997495353221893310546875

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
# 2.68

print(Decimal(2.675).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.67

print(Decimal(str(2.675)).quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN))
# 2.68

请注意,quantize()方法返回的是十进制类型的数字,所以如果你想对一个浮动类型的数字进行操作,你需要用float()将其转换为浮动类型,否则会发生错误。

d = Decimal('123.456').quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

print(d)
# 123.46

print(type(d))
# <class 'decimal.Decimal'>

# print(1.2 + d)
# TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal'

print(1.2 + float(d))
# 124.66

整数四舍五入到任何位数和四舍五入到偶数的方法

如果你想四舍五入到一个整数位,指定像'10'这样的参数作为第一个参数将不会得到你想要的结果。

i = 99518

print(Decimal(i).quantize(Decimal('10'), rounding=ROUND_HALF_UP))
# 99518

这是因为quantize()根据Decimal对象的指数进行四舍五入,但Decimal('10')的指数是0,而不是1。

你可以通过使用E作为指数字符串来指定一个任意的指数(例如,'1E1')。指数的指数可以在as_tuple方法中检查。

print(Decimal('10').as_tuple())
# DecimalTuple(sign=0, digits=(1, 0), exponent=0)

print(Decimal('1E1').as_tuple())
# DecimalTuple(sign=0, digits=(1,), exponent=1)

如果你想使用正常的符号,或者你想在四舍五入后用整数int类型操作,使用int()来转换结果。

print(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP))
# 9.952E+4

print(int(Decimal(i).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 99520

print(int(Decimal(i).quantize(Decimal('1E2'), rounding=ROUND_HALF_UP)))
# 99500

print(int(Decimal(i).quantize(Decimal('1E3'), rounding=ROUND_HALF_UP)))
# 100000

如果参数四舍五入被设置为ROUND_HALF_UP,将发生一般的四舍五入,例如,5将被四舍五入为10。

print('4 =>', int(Decimal(4).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('5 =>', int(Decimal(5).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
print('6 =>', int(Decimal(6).quantize(Decimal('1E1'), rounding=ROUND_HALF_UP)))
# 4 => 0
# 5 => 10
# 6 => 10

当然,如果你把它指定为一个字符串,就没有问题了。

定义一个新的函数

使用十进制模块的方法是准确和安全的,但如果你对类型转换不适应,你可以定义一个新函数来实现一般的四舍五入。

有许多可能的方法,例如,以下函数。

def my_round(val, digit=0):
    p = 10 ** digit
    return (val * p * 2 + 1) // 2 / p

如果你不需要指定位数,并且总是四舍五入到小数点后第一位,你可以使用一个更简单的形式。

my_round_int = lambda x: int((x * 2 + 1) // 2)

如果你需要精确,使用小数是比较安全的。

以下内容仅作参考。

将小数四舍五入到任何位数。

print(int(my_round(f)))
# 123

print(my_round_int(f))
# 123

print(my_round(f, 1))
# 123.5

print(my_round(f, 2))
# 123.46

与四舍五入不同,按照一般的四舍五入,0.5变成1。

print(int(my_round(0.4)))
print(int(my_round(0.5)))
print(int(my_round(0.6)))
# 0
# 1
# 1

将整数四舍五入到任何位数

i = 99518

print(int(my_round(i, -1)))
# 99520

print(int(my_round(i, -2)))
# 99500

print(int(my_round(i, -3)))
# 100000

与四舍五入不同,按照常见的四舍五入法,5变成了10。

print(int(my_round(4, -1)))
print(int(my_round(5, -1)))
print(int(my_round(6, -1)))
# 0
# 10
# 10

注:对于负值

在上面的例子函数中,-0.5被四舍五入为0。

print(int(my_round(-0.4)))
print(int(my_round(-0.5)))
print(int(my_round(-0.6)))
# 0
# 0
# -1

对于负值的四舍五入有多种思考方式,但如果你想把-0.5变成-1,你可以按如下方式修改,例如

import math

def my_round2(val, digit=0):
    p = 10 ** digit
    s = math.copysign(1, val)
    return (s * val * p * 2 + 1) // 2 / p * s

print(int(my_round2(-0.4)))
print(int(my_round2(-0.5)))
print(int(my_round2(-0.6)))
# 0
# -1
# -1
Copied title and URL