行则将至

人生在勤,不索何获

0%

python 数据精度

因为二进制表示浮点数的特殊性, 浮点数误差在计算机编程中普遍存在, 参考: 浮点数精度问题及解决方案 下面是 Python 语言的浮点数精度问题和解决方案

python 浮点小数精度问题

  1. 小数的精度问题
1
2
3
4
5
6
7
8
9
# 第一种情况
a = 0.1;
print(a) # 默认精度
print("%.17f \n"%a) # 17位精度

# 第二种情况
b = 1.1 + 2.2
print(b)

输出:

1
2
3
4
0.1 
0.10000000000000001 # 超出16位后就会出现误差

3.3000000000000003 # 小数计算误差

  1. round() 的进位规则问题

在Python 3里面, round对小数的精确度采用 四舍六入五成双的方式。比如 1.15—>1.2, 1.25—>1.2, 1.250—>1.2, 1.25012—>1.3

round给出的结果如果与你设想的不一样,有两个原因:

  1. 你的这个小数在计算机中能不能被精确储存?如果不能,那么它可能并没有达到四舍五入的标准,例如 1.115,它的小数点后第三位实际上是 4,当然会被舍去。
  2. 如果你的这个小数在计算机中能被精确表示,那么, round采用的进位机制是 奇进偶舍,所以这取决于你要保留的那一位,它是奇数还是偶数,以及它的下一位后面还有没有数据。

解决方法

  1. 小数的精度问题 使用 Decimal 表示和计算数字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    from decimal import Decimal

    a = 0.1;
    print(a) # 默认精度
    print(Decimal('0.1')) # 17位精度

    b = Decimal('1.1') + Decimal('2.2')
    print(b)

  2. round() 的进位规则问题 自定义round规则
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    from decimal import Decimal, ROUND_HALF_UP

    def custom_round(number: str, precision: int = 0):
    """
    自定义浮点型四舍五入
    :param number: 输入数值
    :param precision: 浮点型的精度位数, 可能是负数
    :return:
    """
    number_decimal = Decimal(number)
    number_float = float(number)

    sign = 1 # 正负号处理, 默认是正号
    if number_decimal < 0: # 符号变更 (Decimal 的 quantize 不支持负数, 需要先转为正数)
    sign = -1

    number_float = number_float * -1

    if precision <= 0:
    # 整数或者负精度的数字的处理方式
    base = 10 ** precision
    big = number_float * base + 0.5 # 先变小'精度*10'倍, 然后 +0.5 进位
    restore = int(big) / base
    return sign * restore

    precision = str(1 / 10 ** precision)
    return number_decimal.quantize(Decimal(precision), rounding=ROUND_HALF_UP) * sign

    print(f"normal round: {round(3.14159265358979323846, 19)} \tcustom round:{custom_round('3.14159265358979323846', 19)}")
    print(f"normal round: {round(-6.25, -1)} \tcustom round:{custom_round('-6.25', -1)}")
    print(f"normal round: {round(6.25, -2)}\tcustom round:{custom_round('6.25', -2)}")