17. 整数、浮点数和复数
17. 整数、浮点数和复数
Python 的数字对象是具有专门实现的普通对象。它们参与与列表、字典、函数、类和模块相同的对象模型:每个值都有一个对象头、一个类型指针、引用计数行为和用于操作的类型槽。
主要的内置数字类型有:
|类型 | Python 名称 |主要代表|
| -------------- | ----------- | ------------------------ | |
|整数|int|任意精度整数 |
|布尔 |bool|单例子类int|
|浮点数|float| C双|
|复杂|complex|一对C双打|
这些类型在 Python 级别看起来很简单,但每种类型都具有重要的运行时权衡。
17.1 数字对象就是对象
Python 整数通常不会作为原始 CPU 整数存储在 Python 变量中。python x = 42 在 CPython 级别,x指的是Python整型对象。
从概念上讲:text x ---> PyLongObject object header integer payload 对于浮点数和复数也是如此。python a = 1.5 b = 1 + 2j 从概念上讲:```text
a ---> PyFloatObject
object header
double value
b ---> PyComplexObject
object header
double real
double imag
对象模型为数字提供了正常的 Python 行为:python
print(type(42))
print((42).class)
print((42).bit_length())
## 17.2 整数对象
蟒蛇`int`是任意精度。```python
x = 10 ** 100
print(x)
```该值在 32 或 64 位时不会溢出。 CPython 根据需要增长内部表示。
C级类型通常称为`PyLongObject`。
从概念上讲:```text
PyLongObject
PyVarObject header
ob_size
digit array
```这`ob_size`字段对内部位数和符号进行编码。数字数组将绝对值存储在一个大基数中。
这就是为什么 Python 可以计算:```python
x = 2 ** 1000
y = x * x
print(y)
```没有整数溢出。
代价是大整数比机器整数需要更多的内存和更多的 CPU 时间。
## 17.3 整数数字存储
CPython 将整数存储为多个内部数字。
简化模型:```text
value = sign * (d0 + d1 * base + d2 * base^2 + ...)
```对于小整数,可能只需要一位数字。
对于大整数,需要很多位数。
从概念上讲:```text
42
sign = positive
digits = [42]
2**1000
sign = positive
digits = [d0, d1, d2, ...]
```这种表示方式类似于人类将十进制数写成数字的方式,只不过 CPython 使用更大的内部基数来提高效率。
对这些数字数组进行算术运算。```text
small int addition
cheap
large int addition
cost grows with number of digits
large int multiplication
cost grows more quickly, with specialized algorithms for large cases
```## 17.4 整数符号
该符号通过尺寸元数据表示,而不是作为单独的 Python 对象。
从概念上讲:```text
ob_size > 0
positive integer
ob_size == 0
zero
ob_size < 0
negative integer
```数字数组存储幅度。
例子:```text
123
ob_size = positive digit count
digits = magnitude
-123
ob_size = negative digit count
digits = magnitude
```这种表示使对象保持紧凑。
## 17.5 整数不变性
整数是不可变的。```python
x = 10
x += 1
```这不会改变整数对象`10`。它创建或检索另一个整数对象并重新绑定`x`。
从概念上讲:```text
x ---> int object 10
x += 1
x ---> int object 11
```当对象共享时,此行为是可见的:```python
a = 10
b = a
a += 1
print(a) # 11
print(b) # 10
b仍然引用原始整数对象。
17.6 小整数重用
CPython 重用了一些小的整数对象。```python a = 1 b = 1
这是一个优化。它减少了对共同价值观的分配。
不要依赖身份来进行整数值比较。
正确的:```python
if x == 1:
...
```错误:```python
if x is 1:
...
is测试对象身份。==测试数字相等。
17.7 布尔对象bool是一个子类int。```python
print(isinstance(True, int)) # True
print(True + True) # 2
恰好有两个布尔单例对象:python
True
False
类型关系为:text
bool
subclass of int
这是为了历史和实际的兼容性而存在的。布尔值在数字上下文中工作,但代码应该将它们用作真值。python
if ready:
...
而不是:python
if ready == True:
...
在 C 级别,扩展代码通常返回布尔值:c
Py_RETURN_TRUE;
Py_RETURN_FALSE;
## 17.8 真值测试
数字对象参与真值测试。```python
bool(0) # False
bool(1) # True
bool(-1) # True
bool(0.0) # False
bool(0j) # False
```数字类型的规则很简单:```text
zero value
false
nonzero value
true
```真实性测试使用类型协议机制。在 C 级别,数字类型通过槽提供行为,例如布尔转换。
## 17.9 整数运算
常见的整数运算包括:```python
a + b
a - b
a * b
a // b
a % b
a ** b
a << n
a >> n
a & b
a | b
a ^ b
~a
```这些映射到数字槽。
从概念上讲:```text
PyLong_Type
nb_add
nb_subtract
nb_multiply
nb_floor_divide
nb_remainder
nb_power
nb_lshift
nb_rshift
nb_and
nb_or
nb_xor
nb_invert
```每个操作接收Python对象并返回一个Python对象。
即使结果适合机器字,结果也是一个 Python 对象。
## 17.10 整数除法
Python 有两个主要的除法运算符。```python
a / b # true division
a // b # floor division
```对于整数:```python
print(5 / 2) # 2.5
print(5 // 2) # 2
/返回普通整数的浮点数。//返回楼层结果。
对于负值:python print(-5 // 2) # -3 print(-5 % 2) # 1 身份持有:text a == (a // b) * b + (a % b) 和%携带 Python 语义所需的符号约定。
17.11 位运算
整数支持位运算,就像以具有无限符号扩展的二进制补码表示一样。python x & y x | y x ^ y ~x x << n x >> n 例子:python print(5 & 3) # 1 print(5 | 3) # 7 print(5 ^ 3) # 6 print(~5) # -6 结果~x如下:```text
~x == -x - 1
## 17.12 整数方法
整数方法公开内部值的有用属性。```python
x = 1024
print(x.bit_length())
print(x.bit_count())
bit_length返回表示绝对值所需的位数。```python
print((0).bit_length()) # 0
print((1).bit_length()) # 1
print((2).bit_length()) # 2
print((3).bit_length()) # 2
`bit_count`返回人口数量的绝对值。```python
print((7).bit_count()) # 3
```这些方法通常可以有效地映射到可用的内部数字运算和 CPU 内在函数。
## 17.13 整数转换为字节
整数可以与字节序列相互转换。```python
x = 1024
data = x.to_bytes(2, byteorder="big")
print(data)
again = int.from_bytes(data, byteorder="big")
print(again)
```这对于:```text
binary protocols
cryptography
file formats
network byte order
serialization
```字节顺序必须是明确的。```python
x.to_bytes(4, "big")
x.to_bytes(4, "little")
```相同的整数根据字节顺序产生不同的字节布局。
## 17.14 浮动对象
蟒蛇`float`通常是 C double 的包装。
从概念上讲:```text
PyFloatObject
PyObject header
double value
```浮动对象是固定大小的。```python
x = 1.5
y = 2.25
print(x + y)
```浮点运算是通过数字槽和平台浮点运算来实现的。
关键点:浮点数是近似的二进制浮点数。
## 17.15 二进制浮点
小数分数通常无法用二进制精确表示。```python
print(0.1 + 0.2)
```这通常打印:```text
0.30000000000000004
```问题在于代表性。`0.1`没有有限的二进制展开式,就像`1/3`没有有限的十进制展开式。
所以存储的值是最接近的可表示的二进制浮点数。
这会影响平等:```python
print(0.1 + 0.2 == 0.3) # False
```使用基于容差的比较来进行近似数值工作:```python
import math
math.isclose(0.1 + 0.2, 0.3)
```## 17.16 浮点特殊值
浮点数包含特殊值:```python
inf = float("inf")
nan = float("nan")
neg_inf = float("-inf")
```在许多比较中,Infinity 的表现都符合预期:```python
print(inf > 1e308) # True
```NaN 是不寻常的:```python
nan = float("nan")
print(nan == nan) # False
print(nan != nan) # True
```NaN 的意思是“不是数字”。它与自身相比并不等于。
此行为遵循浮点规则,而不是普通的对象标识逻辑。
## 17.17 浮点散列和相等
数字类型协调相等性和散列。```python
print(1 == 1.0) # True
print(hash(1) == hash(1.0))
```如果两个数值对象比较相等,则它们必须具有相同的哈希值。
这使得字典能够正确运行:```python
d = {}
d[1] = "int"
d[1.0] = "float"
print(d)
```第二个作业更新了相同的关键位置,因为`1 == 1.0`。
这种跨类型的数字相等很方便,但它可能会让那些期望类型不同的键的用户感到惊讶。
## 17.18 浮点转换
换算:```python
float(1)
int(1.9)
round(1.9)
int向零截断:```python
print(int(1.9)) # 1
print(int(-1.9)) # -1
`round`遵循 Python 的舍入规则:```python
print(round(2.5))
print(round(3.5))
```如果浮点不能准确表示预期值,则浮点到整数的转换可能会丢失信息。
对于十进制金融算术,请使用`decimal.Decimal`而不是二进制浮点数。
## 17.19 复杂对象
蟒蛇`complex`存储两个浮点值:```text
PyComplexObject
PyObject header
double real
double imag
```例子:```python
z = 3 + 4j
print(z.real)
print(z.imag)
```实部和虚部是浮点数。
复数支持算术:```python
a = 1 + 2j
b = 3 + 4j
print(a + b)
print(a * b)
```他们不支持订购:```python
(1 + 2j) < (3 + 4j) # TypeError
```Python 数值模型中的复数没有自然的全序。
## 17.20 复杂算术
复数乘法如下:```text
(a + bi)(c + di) = (ac - bd) + (ad + bc)i
```Python 在复杂类型实现中处理这个问题。
例子:```python
a = 1 + 2j
b = 3 + 4j
print(a * b) # (-5+10j)
```部门和权力也得到支持。
对于高级数值工作,`cmath`模块提供复杂感知的数学函数。```python
import cmath
print(cmath.sqrt(-1))
```## 17.21 数字塔
Python 的数值模型有一个概念层次结构:```text
numbers.Number
numbers.Complex
numbers.Real
numbers.Rational
numbers.Integral
```内置类型大致如下:
|类型 |概念角色|
| --------- | -------------------------------------- |
|`complex`|复杂|
|`float`|真实|
|`int`|积分 |
|`bool`|实践中的积分子类 |
这`numbers`模块为数字协议提供抽象基类。
大多数普通 CPython 操作使用具体类型槽而不是抽象基类分派,但层次结构有助于定义预期行为。
## 17.22 混合数字运算
混合数字运算遵循强制和调度规则。```python
print(1 + 2.5) # 3.5
print(1 + 2j) # (1+2j)
print(1.5 + 2j) # (3.5+2j)
```典型加宽方向:```text
int -> float -> complex
```这是一个概念模型。实际的实现使用操作数类型之间的二元运算调度。
对于用户定义的数字类,返回`NotImplemented`让另一个操作数尝试反射的行为。```python
class N:
def __add__(self, other):
return NotImplemented
```## 17.23`__index__`
`__index__`意味着一个对象可以被解释为用于索引和切片的精确整数。```python
class Index:
def __index__(self):
return 2
xs = [10, 20, 30, 40]
print(xs[Index()])
```这比`__int__`。
用例包括:```text
list indexes
slice bounds
range arguments
bit operations
low-level integer contexts
```在 C 级别,这对应于整数索引协议行为。
## 17.24`__int__`, `__float__`, 和`__complex__`转换方法允许自定义类似数字的对象转换为内置数字类型。```python
class Value:
def __int__(self):
return 10
def __float__(self):
return 10.5
def __complex__(self):
return 10.5 + 2j
```用法:```python
v = Value()
print(int(v))
print(float(v))
print(complex(v))
```这些转换创建内置数字对象。
它们不会自动将自定义类型设为完全数字。算术方法仍然需要单独实现。
## 17.25 数字槽
数字行为是通过槽实现的。
概念槽表:```text
PyNumberMethods
nb_add
nb_subtract
nb_multiply
nb_remainder
nb_divmod
nb_power
nb_negative
nb_positive
nb_absolute
nb_bool
nb_invert
nb_lshift
nb_rshift
nb_and
nb_xor
nb_or
nb_int
nb_float
nb_index
nb_matrix_multiply
nb_inplace_add
...
```Python 语法映射到此表中。
|语法 |老虎机概念|
| ---------- | -------------------- |
|`a + b`|加法|
|`a - b`|减法|
|`a * b`|乘法|
|`a @ b`|矩阵乘法 |
|`-a`|否定|
|`abs(a)`|绝对值|
|`bool(a)`|真值|
|`int(a)`|整数转换|
|`float(a)`|浮点数转换|
|`a << b`|左移|
这个槽表就是为什么内置和扩展数字类型可以与 Python 语法集成的原因。
## 17.26 就地数值运算
就地运算符包括:```python
x += y
x -= y
x *= y
x //= y
x **= y
```对于不可变数字,就地操作创建一个新对象并重新绑定名称。```python
x = 10
before = id(x)
x += 1
after = id(x)
print(before == after) # usually False
```对于可变的类似数字的对象,类型可以实现真正的就地突变。
内置`int`, `float`, 和`complex`是不可变的。
## 17.27 小数和分数
标准库提供了核心内置函数之外的数字类型。`decimal.Decimal`提供十进制浮点运算。```python
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2"))
fractions.Fraction提供有理算术。```python
from fractions import Fraction
print(Fraction(1, 3) + Fraction(1, 6))
当它们的语义与问题匹配时使用它们:
|需要|类型 |
| ----------------------------------- | ---------- |
|一般整数 |`int`|
|近似科学数值工作|`float`|
|复杂算术 |`complex`|
|十进制金融算术|`Decimal`|
|精确有理算术 |`Fraction`|
## 17.28 C API:创建数字
创建一个整数:```c
PyObject *x = PyLong_FromLong(42);
if (x == NULL) {
return NULL;
}
```从更广泛的 C 类型创建:```c
PyObject *x = PyLong_FromLongLong(value);
```创建浮动:```c
PyObject *f = PyFloat_FromDouble(3.14);
if (f == NULL) {
return NULL;
}
```创建一个综合体:```c
PyObject *z = PyComplex_FromDoubles(1.0, 2.0);
if (z == NULL) {
return NULL;
}
```每个都返回一个新的引用。调用者拥有它。
## 17.29 C API:提取数字
提取 C 长:```c
long value = PyLong_AsLong(obj);
if (value == -1 && PyErr_Occurred()) {
return NULL;
}
```错误检查很重要,因为`-1`可以是一个有效的结果。
提取双精度数:```c
double value = PyFloat_AsDouble(obj);
if (value == -1.0 && PyErr_Occurred()) {
return NULL;
}
```再次检查异常状态。
对于类似整数的上下文,当需要精确的整数语义时,请使用索引转换 API。
## 17.30 C 边界溢出
Python 整数是任意精度的。 C 整数是固定宽度的。
此转换可能会失败:```c
long value = PyLong_AsLong(obj);
```如果`obj`对于 C 来说太大`long`。
Python级别的例子:```python
x = 10 ** 100
```该值作为 Python 有效`int`,但可能不适合任何 C 整数类型。
C 扩展代码必须处理溢出错误。
一个常见的错误是假设 Python`int`总是适合`int`, `long`, 或者`size_t`。
## 17.31 性能说明
数字表现取决于代表性。
小整数算术已优化,但仍可在 Python 对象上运行。
紧密循环:```python
total = 0
for i in range(10_000_000):
total += i
```执行许多 Python 级别的操作:```text
load total
load i
integer addition
create or retrieve result integer
store total
loop control
```这比原始机器整数上的等效 C 慢得多。
对于大型数值数组,请使用紧凑地存储原始值的专用库,例如`array`, `memoryview`,或 NumPy。
Python 的内置数值类型优化标量语义,而不是密集数值向量计算。
## 17.32 记忆笔记
整数列表存储对整数对象的引用。```python
xs = [1, 2, 3, 4]
```从概念上讲:```text
list
[ptr][ptr][ptr][ptr]
| | | |
v v v v
int int int int
```数组存储原始值:```python
from array import array
xs = array("i", [1, 2, 3, 4])
```从概念上讲:```text
array
[int][int][int][int]
```这就是为什么数组对于大型同质数值数据来说内存效率更高。
## 17.33 常见的数字陷阱
|陷阱|示例|更好的方法|
| -------------------------------------- | ------------------------------------------- | ------------------------ | |
|使用`is`对于数字|`x is 1000` | `x == 1000`|
|期望 float | 的小数精度`0.1 + 0.2 == 0.3` | `math.isclose`或者`Decimal`|
|坚持`hash()` | `hash(x)`作为稳定ID |`hashlib`|
|假设 Python int 适合 C long |`PyLong_AsLong`未经检查 |检查溢出|
|将列表用于密集数值数组 |`[0] * n`对于巨大的数字数据|`array`或 NumPy |
|正常比较 NaN |`nan == nan`|使用`math.isnan`|
|忽略整数增长成本 |热路径中的巨大力量|考虑算法边界 |
## 17.34 心智模型
使用这个模型:```text
int
immutable arbitrary-precision integer
variable-size digit array
no fixed overflow
cost grows with magnitude
bool
singleton subclass of int
truth-value type
values are True and False
float
immutable wrapper around C double
approximate binary floating-point
supports inf and nan
complex
immutable pair of doubles
real and imaginary parts
arithmetic supported
ordering unsupported
```在 CPython 级别:```text
numeric operation
dispatch through type slots
operate on concrete numeric representation
allocate or return result object
manage references
```## 17.35 总结
CPython 将整数、浮点数和复数实现为具有专门 C 布局的 Python 对象。`int`使用任意精度存储,因此可以避免固定宽度溢出,但会付出随值大小而增长的成本。`float`包装 C double 并继承二进制浮点行为。`complex`存储两个双精度数并支持复杂算术。
这些数字类型是不可变的。操作创建结果对象而不是改变操作数。它们的行为通过数字槽、转换协议、散列规则和 C API 函数进行集成。