Python 三种余数:同样是七除以四,remainder 为什么会给出负一

前天 3阅读

周期位置、相位差和循环索引都可能需要余数,但 Python 的百分号、math.fmod 和 math.remainder 并不承诺同一个结果。七除以四,前两者可以得到三,remainder 却得到负一;差异首先来自选了哪个整数商。

下面只用很小、可精确表示的浮点数,隔离规则差异。保存为 demo.py,运行 python demo.py。每行同时打印三种结果,并用明确的预期值核对,另检查半整数商、带符号零和除数为零的边界。

Python 三种余数:同样是七除以四,remainder 为什么会给出负一

AI模型生成概念示意:三种取余约定选择不同参考位置,余量的方向也随之变化;图中轨道不是带精确数值的实测坐标。

import math

cases = [
    (-7.0, 4.0, (1.0, -3.0, 1.0)),
    (7.0, 4.0, (3.0, 3.0, -1.0)),
    (6.0, 4.0, (2.0, 2.0, -2.0)),
    (10.0, 4.0, (2.0, 2.0, 2.0)),
]
for x, y, expected in cases:
    actual = (x % y, math.fmod(x, y), math.remainder(x, y))
    assert actual == expected
    assert abs(actual[2]) <= abs(y) / 2
    print(x, y, "->", actual)

zero = math.remainder(-8.0, 4.0)
assert zero == 0.0 and math.copysign(1.0, zero) == -1.0
print("zero:", zero, "sign:", math.copysign(1.0, zero))
try:
    math.remainder(1.0, 0.0)
except ValueError:
    print("zero divisor: ValueError")
else:
    raise AssertionError("zero divisor was accepted")

先选整数商,再看留下多少

第一行 -7.0 4.0 得到 (1.0, -3.0, 1.0)。百分号使用向下取整的商,负七除四对应商负二,余数为一;fmod 的商向零截断,商为负一,余数就是负三。因此混合正负输入时,不能把 fmod 当作百分号的另一种拼写。

第二行 7.0 4.0 得到 (3.0, 3.0, -1.0)。remainder 选择最接近精确商的整数:一点七五靠近二,七减去二乘四便为负一。对有限输入及有限非零除数,它把余数绝对值控制在除数绝对值的一半以内,适合需要围绕零表达偏差的某些计算。

恰好半圈时,还要说明端点规则

六除四与十除四分别产生一点五、二点五两个半整数商。remainder 都选择偶数商二,所以对应结果一个是负二,一个是正二;实跑第三、四行确认了这一点。不能把它描述成固定返回某个半开区间,因为两端都可能出现。

如果应用要求相差半个周期时始终选同一方向,还需要在计算后按自己的端点约定转换,并把六、十及其负值放进测试。算法的数学区间、界面期望的方向和库函数的中点规则,应在接口里写清楚。

零和浮点精度仍然需要边界检查

零的输出是 zero: -0.0 sign: -1.0。普通相等比较会把正负零视为相等,copysign 才显式展示符号。remainder 为零时保留被除数的符号;若下游格式不接受负零,可以在展示层按约定处理,别据打印形式误判计算没有归零。

除数为零时,示例捕获到 ValueError。面向业务输入还应明确是否允许无穷与 NaN,本教程的主要推导限定为有限输入。整数循环索引通常直接使用整数取模;将巨大整数先转成 float,会先丢失输入精度。

官方对 IEEE 754 二进制浮点平台说明 remainder 的结果可精确表示,这指的是函数收到的浮点值,不代表原始十进制文本从未近似。排查差一圈或符号相反时,先验证商和端点规则,再检查输入转换;盲目增加小数位无法修复选错运算的语义问题。

资料核对日期:2026年10月2日。示例在本地 Python 3.12.14 实际执行,全部断言通过;输出对应文中固定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。