NTT(数论变换,Number Theoretic Transform)
一、NTT的数学原理
NTT是快速傅里叶变换(FFT)在模数域下的等价形式。它主要用于大整数乘法等问题中,尤其是在模数运算环境下。NTT的优点在于它避免了浮点数运算,减少了误差的积累,特别适用于加密和编码领域。
1.1 知识点补充
1.1.1模数运算环境
模数运算是指在取余运算的过程中,只考虑余数而忽略商的计算。在模数运算中,我们通常使用一个模数(模)来对一个数进行取余运算。
举个简单的例子,具体的计算步骤如下:
假设我们要计算13对5取模的结果,我们要计算 13 mod 5
首先,我们将13除以5,得到商2和余数3。
在模数运算中,我们只关心余数,因此13 mod 5 的结果为3。
因此,13 mod 5 的结果是3。在计算机科学和密码学领域,模数运算经常被使用,特别是在加密算法和校验方法中。
1.1.2 符号
- 在数学中,符号 “|” 通常表示“整除”。所以当我说到 “n | (p-1)” 时,意思是 n 能够整除 (p-1),也就是说,(p-1) 是 n 的倍数。
- List item
1.1.3 原根和
- 原根(Primitive Root):在数论中,给定一个正整数n,如果存在一个整数a,使得对于任意与n互质的正整数k,都存在一个正整数m,使得a^m≡k ( mod n) ,那么a就被称为模n的原根。换句话说,原根是一个可以生成模n剩余类的整数。
举个例子,考虑模12的情况,我们可以计算出2、5和11都是模12的原根。例如,以2为底的幂次运算:
2^1≡2 ( mod 12)
2^2≡4 ( mod 12)
2^3≡8 ( mod 12)
2^4≡4 ( mod 12)
2^5≡10 ( mod 12)
2^6≡2 ( mod 12)...
我们可以看到,对于与12互质的任何数k,总能找到一个m,使得2m≡k(mod12)2m≡k(mod12)。因此,2是模12的原根。
-
单位根(Unit Root):在复数域中,给定一个正整数n,单位根是指满足方程xn=1xn=1的复数解。单位根的个数为n,可以用复数平面的单位圆上的点来表示。
举个例子,考虑单位根的情况,我们来看看模5的单位根。方程x5=1x5=1有五个根,分别是1、e2πi/5e2πi/5、e4πi/5e4πi/5、e6πi/5e6πi/5和e8πi/5e8πi/5,其中ee是自然对数的底,i是虚数单位。这五个复数都满足x5=1x5=1的条件,因此它们都是模5的单位根。
总结:原根是数论中与模n的剩余类相关的概念,单位根是复数域中与方程xn=1xn=1相关的概念。两者在不同领域的数学中有不同的含义和应用。
1.2 原理概述
1.2.1 单位根
在NTT中,我们使用的是n阶原根的幂作为单位根,而不是FFT中的复数单位根。如果有一个数ω满足ω^n ≡ 1 (mod p),其中p是质数,且n | (p-1),那么称ω是模p的n次本原单位根。
1.2.2 DFT和IDFT
NTT基于离散傅里叶变换(DFT)和其逆变换(IDFT)的概念,但是在有限域(模p)中进行计算。对于一个长度为n的序列a,其NTT变换定义为另一个序列A,其中A[k] = Σ(a[j] * ω^(jk)) mod p,j从0到n-1。逆变换则用ω的逆元进行计算。
1.2.3 蝴蝶操作
NTT和FFT一样,通过分治策略实现快速计算。这个过程中的基本操作被称为“蝴蝶操作”,它将序列分为“偶数部分”和“奇数部分”,然后递归地应用变换,最后合并结果。
1.2.4 模数选择
为了使NTT正确运行,模数p需要满足特定条件:p = kn + 1,其中k是正整数,这保证了n阶原根的存在。此外,为了简化逆元和模逆的计算,通常选择p使得它是一个质数。
1.3 示例
假设我们要对序列a = [1, 2, 3, 4]进行NTT变换,选择n=4,p=17(一个满足条件的模数,因为17-1可以被4整除),并找到一个4次本原单位根ω=3(因为3^4 ≡ 1 (mod 17))。
计算A[0]:
A[0] = 13^0 + 23^0 + 33^0 + 43^0 = 10 (mod 17)
计算A[1]:
A[1] = 13^0 + 23^1 + 33^2 + 43^3 = 1 + 6 + 9 + 8 = 24 ≡ 7 (mod 17)
计算A[2]:
A[2] = 13^0 + 23^2 + 33^4 + 43^6 = 1 + 9 + 3 + 4 = 17 ≡ 0 (mod 17)
计算A[3]:
A[3] = 13^0 + 23^3 + 33^6 + 43^9 = 1 + 8 + 4 + 1 = 14 (mod 17)
所以,NTT变换的结果是A = [10, 7, 0, 14]。
1.4 简单算法
用Python编写的NTT算法示例:
python
def ntt(a, p, omega):
“”"
NTT算法实现
参数:
a -- 输入的序列
p -- 模数
omega -- 本原单位根
返回值:
A -- NTT变换后的结果序列
"""
n = len(a)
if n == 1:
return a
# 分治:将输入序列分为偶数部分和奇数部分
even = ntt(a[0::2], p, omega) # 偶数部分
odd = ntt(a[1::2], p, omega) # 奇数部分
# 合并结果
A = [0] * n
factor = 1
for k in range(n//2):
t = (odd[k] * factor) % p
A[k] = (even[k] + t) % p
A[k + n//2] = (even[k] - t) % p
factor = (factor * omega) % p
return A
示例用法
a = [1, 2, 3, 4]
p = 17
omega = 3
A = ntt(a, p, omega)
print(A)
这段代码中,ntt 函数使用递归方式实现了NTT算法。它首先检查输入序列的长度,如果长度为1,则直接返回该序列。否则,它将输入序列分为偶数部分和奇数部分,然后递归地对它们进行NTT变换。最后,通过合并结果得到最终的NTT变换序列。
在示例中,我们使用了输入序列 [1, 2, 3, 4],模数 p = 17,本原单位根 omega = 3。运行代码后,将会输出NTT变换后的结果序列。
至此,我还没完全学明白,我还要再摸索摸索