3.2 dtype家族与安全转型


文档摘要

3.2 dtype家族与安全转型 本节摘要:dtype 名由类型加位宽构成(int32 占 4 字节、float64 占 8 字节),决定每个元素的解释方式与内存宽度。astype 转型必然产生拷贝;窄化转型会静默截断,casting 参数可以把这类操作拦下来报错。本节给出字宽速查、float32 与 float64 的取舍实验,以及三个真实转型事故。 dtype 名怎么读 dtype 命名是"类型 + 位宽"的直白组合,位宽就是每个元素占的比特数,除以 8 得字节数: dtype | 字节 | 范围或精度 | 典型场景 int8 | 1 | -128 到 127 | 灰度图像像素 int16 | 2 | 约 ±3.

3.2 dtype家族与安全转型

本节摘要:dtype 名由类型加位宽构成(int32 占 4 字节、float64 占 8 字节),决定每个元素的解释方式与内存宽度。astype 转型必然产生拷贝;窄化转型会静默截断,casting 参数可以把这类操作拦下来报错。本节给出字宽速查、float32 与 float64 的取舍实验,以及三个真实转型事故。

dtype 名怎么读

dtype 命名是"类型 + 位宽"的直白组合,位宽就是每个元素占的比特数,除以 8 得字节数:

dtype 字节 范围或精度 典型场景
int8 1 -128 到 127 灰度图像像素
int16 2 约 ±3.3 万 音频采样
int32 4 约 ±21 亿 Windows 默认整数
int64 8 约 ±9.2×10^18 多数平台默认整数
uint8 1 0 到 255 图像通道、掩码
float16 2 3 位有效数字 深度学习省内存
float32 4 约 7 位有效数字 科学计算、GPU
float64 8 约 15 至 16 位 通用浮点默认
bool 1 True/False 掩码过滤
complex128 16 实部虚部各 float64 信号处理、频谱

第 1 章的公式在这里直接生效:nbytes = size × itemsize。dtype 选窄一半,内存就省一半,但精度也跟着降。

import numpy as np a = np.arange(1000) # int64 b = a.astype(np.int16) # 转窄 print(a.nbytes, b.nbytes) # 8000 2000 print(np.float32(0.1) + np.float32(0.2)) # 0.3(打印看不出差别) print(np.float32(0.1) + np.float32(0.2) == np.float32(0.3)) # False! # float32 只有约 7 位有效数字,0.1 在它眼里并不精确

float32 与 float64 的精度现场

float32 与 float64 的精度现场

astype:必然拷贝的转型

dtype 变了,字节宽度就变了,旧数据块装不下新解释——所以 astype 永远返回新数组:

import numpy as np f = np.array([1.7, 2.9, -3.5]) i = f.astype(np.int32) print(i) # [ 1 2 -3] —— 截断,不是四舍五入 print(np.shares_memory(i, f)) # False,新内存 # 想四舍五入要先 round 再转 i2 = np.round(f).astype(np.int32) print(i2) # [ 2 3 -4](-3.5 四舍五入到偶数是 -4)

注意 astype 的浮转整是"向零截断",1.9 变 1。更危险的是静默溢出:

big = np.array([200, 300, 400], dtype=np.int64) small = big.astype(np.int8) print(small) # [-56 44 -112] —— 溢出环绕,不报错! # casting 参数可以拦截:unsafe 是默认,no/even 会校验 try: np.array([200, 300]).astype(np.int8, casting="same_kind") # same_kind 允许同类内转,int64 到 int8 不会触发 except (TypeError, ValueError) as e: print("被拦截:", e)

等等,same_kind 允许 int64 转 int8,真正能拦住的是显式检查:

def safe_cast(arr, target): info = np.iinfo(target) if np.issubdtype(target, np.integer) else None if info and (arr.min() < info.min or arr.max() > info.max): raise ValueError(f"{arr.dtype} 到 {target} 会溢出,范围不符") return arr.astype(target) print(safe_cast(np.array([1, 2]), np.int8)) # [1 2] 正常 # safe_cast(big, np.int8) 会抛 ValueError,提前拦截

三个真实转型事故

事故一:像素坐标溢出。背景:图像处理流水线里坐标数组一直用 int32,某天拼接全景图坐标超过 21.5 亿像素。操作:两 int32 相加。结果:值环绕成负数,拼接逻辑越走越偏。解读:int32 相加的结果仍是 int32(NumPy 不会自动升 int64),溢出静默发生。变式修复:参与大数运算前 astype(np.int64)。

事故二:平均值变成 0。背景:整型数组的均值。

import numpy as np d = np.array([3, 4, 6, 7], dtype=np.int32) print(d.mean()) # 5.0 —— NumPy 的 mean 内部已升 float,没事 print(d // 2) # [1 2 3 3] —— 整除丢小数,预期外时是坑

Python 3 的 / 得 float,但 // 保留整型。很多"数值莫名变小"的 bug 出在把 / 写成 //。

事故三:None 混入炸出 object。背景:手工整理的数据里有一个缺失值写成 None。

import numpy as np dirty = np.array([1.5, 2.0, None, 3.5]) print(dirty.dtype) # object! print(dirty * 2) # 能跑但逐对象调用,慢且行为怪 # 修复:显式 float 转型,None 变 nan clean = np.array([1.5, 2.0, None, 3.5], dtype=np.float64) # 抛错:None 无法转 float64,需先清洗 values = [x if x is not None else np.nan for x in [1.5, 2.0, None, 3.5]] clean = np.array(values) print(clean) # [1.5 2. nan 3.5]

⚠️ 常见坑:看到 dtype 是 object 就要停下。object 数组退化为指针数组,第 1 章列表的所有性能问题原样回归,且很多 ufunc 直接不可用。

本节要点回顾

  • 命名规则:类型加位宽,itemsize 即字节数;内存总量等于元素数乘 itemsize
  • 转型即拷贝:astype 换宽度必动内存;浮转整是向零截断,不是四舍五入
  • 静默溢出:int64 转 int8 环绕不报错,窄化前用 iinfo 检查范围
  • 精度纪律:计算过程保 float64,存储传输再降 float32;对 // 的整型结果保持警惕
  • object 警报:dtype 出现 object 说明数据不纯,先清洗再入数组

下一节补齐序列生成与随机数:arange 与 linspace 的分工,以及新式随机接口 Generator 的正确打开方式。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U