import math
import cmath
class FFTNTTSimple:
def __init__(self):
pass
def cooley_tukey_fft(self, coeffs, inverse=False):
n = len(coeffs)
if n <= 1:
return coeffs
if n & (n - 1) != 0:
next_power = 1 << (n - 1).bit_length()
coeffs = coeffs + [0] * (next_power - n)
n = next_power
result = [0] * n
for i in range(n):
j = 0
for k in range(int(math.log2(n))):
if i & (1 << k):
j |= 1 << (int(math.log2(n)) - 1 - k)
result[j] = coeffs[i]
size = 2
while size <= n:
half_size = size // 2
angle = (2 if inverse else -2) * math.pi * 1j / size
for i in range(0, n, size):
w = 1 + 0j for j in range(half_size):
u = result[i + j]
v = result[i + j + half_size] * w
result[i + j] = u + v
result[i + j + half_size] = u - v
w *= cmath.exp(angle)
size *= 2
if inverse:
result = [x / n for x in result]
return result
def polynomial_multiply_fft(self, a_coeffs, b_coeffs):
n = 1
while n < len(a_coeffs) + len(b_coeffs) - 1:
n *= 2
a_padded = a_coeffs + [0] * (n - len(a_coeffs))
b_padded = b_coeffs + [0] * (n - len(b_coeffs))
a_complex = [complex(float(x), 0.0) for x in a_padded]
b_complex = [complex(float(x), 0.0) for x in b_padded]
a_fft = self.cooley_tukey_fft(a_complex)
b_fft = self.cooley_tukey_fft(b_complex)
c_fft = [x * y for x, y in zip(a_fft, b_fft)]
c_complex = self.cooley_tukey_fft(c_fft, inverse=True)
result = []
for x in c_complex:
if hasattr(x, 'real_part'):
real_val = float(x.real_part())
elif hasattr(x, 'real'):
real_val = float(x.real)
else:
real_val = float(x)
rounded = round(real_val)
if abs(real_val - rounded) > 0.1: print(f"Warning: large precision issue at {real_val}")
result.append(int(rounded))
while result and result[-1] == 0:
result.pop()
return result
def big_int_to_coeffs(self, n, base=10):
if n == 0:
return [0]
coeffs = []
while n > 0:
coeffs.append(int(n % base))
n //= base
return coeffs
def coeffs_to_big_int(self, coeffs, base=10):
result = 0
for i, coeff in enumerate(coeffs):
result += coeff * (base ** i)
return result
def multiply_big_ints_fft(self, a, b, base=10):
a_coeffs = self.big_int_to_coeffs(a, base)
b_coeffs = self.big_int_to_coeffs(b, base)
product_coeffs = self.polynomial_multiply_fft(a_coeffs, b_coeffs)
return self.coeffs_to_big_int(product_coeffs, base)
def test_basic():
print("Testing basic FFT/NTT functionality...")
mult = FFTNTTSimple()
print("\nTesting polynomial multiplication:")
a = [1, 2, 3] b = [4, 5]
result = mult.polynomial_multiply_fft(a, b)
expected = [4, 13, 22, 15]
print(f"a = {a}")
print(f"b = {b}")
print(f"Result: {result}")
print(f"Expected: {expected}")
print(f"Correct: {result == expected}")
print("\nTesting big integer multiplication:")
test_cases = [
(0, 0),
(1, 1),
(12, 34),
(123, 456),
]
for x, y in test_cases:
expected = x * y
result = mult.multiply_big_ints_fft(x, y)
correct = (result == expected)
print(f"{x} * {y} = {result} (expected {expected}) {'✓' if correct else '✗'}")
if __name__ == "__main__":
test_basic()