posmod
2026/4/15大约 5 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — posmod
posmod
定义
posmod() 是一个正数取模函数——也就是做取余运算,但结果始终是非负数(大于等于 0)。
你可能已经用过普通的取余运算符 %,但它有一个"坑":当被除数是负数时,% 也会返回负数。比如 -7 % 3 的结果是 -1,而不是 2。在某些场景下(比如循环索引、环形数组、时钟计算),你需要结果总是正的,这时候 posmod 就派上用场了。
打个生活中的比方:想象一个 12 小时制的时钟。现在指针指向 10 点,你想"倒退 15 个小时"。普通取余告诉你 (-5) % 12 = -5(意思是"倒退 5 小时"),但 posmod(-5, 12) 会告诉你 7(意思是"指针最终停在 7 点")。不管时针正着转还是倒着转,posmod 总能帮你算出指针最终落在了表盘的哪个位置。
函数签名
C#
public static int PosMod(int a, int b)GDScript
func posmod(a: int, b: int) -> int参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
a | int | 是 | 被除数——可以是正数、负数或零 |
b | int | 是 | 除数——不能为 0,否则会导致除零错误 |
返回值
int —— a 除以 b 的正数取模结果。结果始终大于等于 0,小于 b 的绝对值。
对比普通取余 % 和 posmod 的区别:
| 表达式 | %(普通取余) | posmod(正数取模) |
|---|---|---|
7 % 3 | 1 | 1 |
-7 % 3 | -1 | 2 |
7 % -3 | 1 | 1 |
0 % 3 | 0 | 0 |
代码示例
C#
// ===== 基础用法:对比 % 和 posmod =====
using Godot;
public partial class PosModExample : Node
{
public override void _Ready()
{
// 正数情况:两者结果一样
int r1 = 7 % 3;
// 运行结果: r1 = 1
int r2 = Mathf.PosMod(7, 3);
// 运行结果: r2 = 1
// 负数情况:结果不同!
int r3 = -7 % 3;
// 运行结果: r3 = -1(普通取余,结果为负)
int r4 = Mathf.PosMod(-7, 3);
// 运行结果: r4 = 2(正数取模,结果为正)
// 可以理解为:-7 = (-3) × 3 + 2
// 零的情况
int r5 = Mathf.PosMod(0, 3);
// 运行结果: r5 = 0
}
}
// ===== 实际场景:环形数组索引(循环访问数组元素) =====
// 一个角色有 5 个武器槽,按"上一个/下一个"循环切换
public partial class WeaponWheel : Node
{
[Export] public string[] ExWeapons = { "剑", "弓", "法杖", "盾", "匕首" };
private int _currentIndex = 0;
// 切换到上一个武器(倒序循环)
public void PreviousWeapon()
{
// _currentIndex - 1 可能为 -1
// PosMod(-1, 5) = 4,正好跳到最后一个
_currentIndex = Mathf.PosMod(_currentIndex - 1, ExWeapons.Length);
GD.Print($"切换到: {ExWeapons[_currentIndex]}");
}
// 切换到下一个武器(正序循环)
public void NextWeapon()
{
_currentIndex = (_currentIndex + 1) % ExWeapons.Length;
GD.Print($"切换到: {ExWeapons[_currentIndex]}");
}
}
// 模拟调用(假设当前在索引 0 "剑"):
// PreviousWeapon() → 运行结果: 切换到: 匕首(索引从 0 跳到 4)
// NextWeapon() → 运行结果: 切换到: 弓(索引从 0 到 1)
// ===== 进阶用法:时钟式索引计算 =====
// 模拟一个 12 格的转盘,支持正向和反向旋转任意步数
public int GetDialPosition(int current, int steps, int dialSize)
{
return Mathf.PosMod(current + steps, dialSize);
}
// 测试:
// GetDialPosition(10, 5, 12) → 运行结果: 3(10+5=15,15%12=3)
// GetDialPosition(2, -5, 12) → 运行结果: 9(2-5=-3,posmod(-3,12)=9)
// GetDialPosition(0, 24, 12) → 运行结果: 0(转两圈回到原点)GDScript
# ===== 基础用法:对比 % 和 posmod =====
extends Node
func _ready():
# 正数情况:两者结果一样
var r1 = 7 % 3
# 运行结果: r1 = 1
var r2 = posmod(7, 3)
# 运行结果: r2 = 1
# 负数情况:结果不同!
var r3 = -7 % 3
# 运行结果: r3 = -1(普通取余,结果为负)
var r4 = posmod(-7, 3)
# 运行结果: r4 = 2(正数取模,结果为正)
# 可以理解为:-7 = (-3) × 3 + 2
# 零的情况
var r5 = posmod(0, 3)
# 运行结果: r5 = 0
# ===== 实际场景:环形数组索引(循环访问数组元素) =====
# 一个角色有 5 个武器槽,按"上一个/下一个"循环切换
@export var ex_weapons: Array[String] = ["剑", "弓", "法杖", "盾", "匕首"]
var _current_index: int = 0
# 切换到上一个武器(倒序循环)
func previous_weapon() -> void:
# _current_index - 1 可能为 -1
# posmod(-1, 5) = 4,正好跳到最后一个
_current_index = posmod(_current_index - 1, ex_weapons.size())
print("切换到: ", ex_weapons[_current_index])
# 切换到下一个武器(正序循环)
func next_weapon() -> void:
_current_index = (_current_index + 1) % ex_weapons.size()
print("切换到: ", ex_weapons[_current_index])
# 模拟调用(假设当前在索引 0 "剑"):
# previous_weapon() → 运行结果: 切换到: 匕首(索引从 0 跳到 4)
# next_weapon() → 运行结果: 切换到: 弓(索引从 0 到 1)
# ===== 进阶用法:时钟式索引计算 =====
# 模拟一个 12 格的转盘,支持正向和反向旋转任意步数
func get_dial_position(current: int, steps: int, dial_size: int) -> int:
return posmod(current + steps, dial_size)
# 测试:
# get_dial_position(10, 5, 12) → 运行结果: 3(10+5=15,15%12=3)
# get_dial_position(2, -5, 12) → 运行结果: 9(2-5=-3,posmod(-3,12)=9)
# get_dial_position(0, 24, 12) → 运行结果: 0(转两圈回到原点)注意事项
- 除数不能为 0:
posmod(a, 0)会导致除零错误,程序会崩溃。使用前请确保除数不为零。 - 与
%的区别仅在负数时体现:当a为正数时,posmod(a, b)和a % b的结果完全相同。只有当a为负数时,posmod才会把结果"翻转"为正数。 - 浮点数版本
fposmod:如果你需要对浮点数做正数取模,请使用fposmod()函数,它和posmod的逻辑一样,只是处理的是float类型。 - 数学原理:
posmod(a, b)的结果等价于(a % b + b) % b。如果你在其他编程语言中需要同样的功能,可以直接用这个公式。 - 典型用途:
posmod最常用于循环索引、环形缓冲区、角度归一化、网格坐标取模等场景——只要你想确保"取余结果一定是正数",就用它。
