Understanding the Zeta Function
The Riemann Zeta Function, denoted as $\zeta(s)$, is a mathematical function that takes a complex number $s$ as input and sums an infinite series. For a basic understanding, when $s$ is a real number greater than 1, it can be written as:
In simpler terms, it adds up the reciprocals of all positive integers raised to the power $s$. For example:
- If $s = 2$, then $\zeta(2) = 1 + \frac{1}{4} + \frac{1}{9} + \cdots = \frac{\pi^2}{6} \approx 1.64493$
The Critical Strip
However, in the case of Riemann, we are interested in complex numbers. The function only reveals its power in the complex plane. In fact, it is most interesting where the real part of the complex number is greater than zero but less than one - known as the critical strip.
The critical strip is the region where: $0 < \text{Re}(s) < 1$
Why here? Outside this strip:
- For $\text{Re}(s) > 1$, the series converges trivially.
- For $\text{Re}(s) \leq 0$, the function is well-understood via analytic continuation (e.g., $\zeta(-1) = -\frac{1}{12}$).
- But in $0 < \text{Re}(s) < 1$, the behaviour of $\zeta(s)$ - especially its zeros - is mysterious and tied to prime numbers.
The Critical Line
The code below focuses on an even more specific case where the function has real part $\frac{1}{2}$. This is the critical line where Riemann hypothesized all non-trivial zeros lie.
Try running this code with 10,000 terms and imaginary part 14.134725. It should get you close to zero - this is the first non-trivial zero of the zeta function!
C++ Implementation
#include <iostream>
#include <complex>
#include <cmath>
using namespace std;
// Dirichlet series for Zeta (Re(s) > 1)
complex<double> zetaDirichlet(complex<double> s, int terms) {
// Note: Simple sum for Re(s) > 1
complex<double> sum(0.0, 0.0);
for (int n = 1; n <= terms; n++) {
sum += pow(1.0 / static_cast<double>(n), s);
}
return sum;
}
// Alternating series for Zeta in critical strip (Re(s) > 0)
complex<double> zetaAlternating(complex<double> s, int terms) {
// Note: Uses zeta(s) = (1 - 2^(1-s))^(-1) * sum ((-1)^(n-1) / n^s)
// Converges for Re(s) > 0, including critical line Re(s) = 1/2
complex<double> sum(0.0, 0.0);
for (int n = 1; n <= terms; n++) {
double sign = (n % 2 == 0) ? -1.0 : 1.0; // Alternates (-1)^(n-1)
sum += sign * pow(1.0 / static_cast<double>(n), s);
}
complex<double> factor = 1.0 / (1.0 - pow(2.0, 1.0 - s));
return factor * sum;
}
// Riemann Zeta Function
complex<double> riemannZeta(complex<double> s, int terms) {
double re_s = real(s);
if (re_s > 1.0) {
// Note: Use Dirichlet series for Re(s) > 1
return zetaDirichlet(s, terms);
} else if (re_s > 0.0) {
// Note: Use alternating series for 0 < Re(s) <= 1 (critical strip)
return zetaAlternating(s, terms);
} else {
// Note: For Re(s) <= 0, we'd need functional equation
cout << "Re(s) <= 0 not implemented in this demo." << endl;
return complex<double>(0.0, 0.0);
}
}
int main() {
int terms;
double t;
cout << "Enter the number of terms for approximation: ";
cin >> terms;
cout << "Enter the imaginary part t (for s = 1/2 + it): ";
cin >> t;
// Note: s = 0.5 + it is on the critical line
complex<double> s(0.5, t);
complex<double> zeta = riemannZeta(s, terms);
cout << "s = " << s << " (Re(s) = " << real(s) << ", Im(s) = " << imag(s) << ")" << endl;
cout << "Zeta(s) approximation with " << terms << " terms: " << zeta << endl;
cout << "Magnitude: " << abs(zeta) << endl;
// Note: Check if close to a zero
if (abs(zeta) < 0.001) {
cout << "This is very close to a non-trivial zero!" << endl;
}
return 0;
}
Compiling & Running
# Compile
g++ -std=c++17 -O2 zeta.cpp -o zeta
# Run
./zeta
# Try these inputs:
# Terms: 10000
# Imaginary part: 14.134725
# (This is the first non-trivial zero!)
First Non-Trivial Zeros
The first few non-trivial zeros of the zeta function (on the critical line $s = \frac{1}{2} + it$) are at approximately:
- $t_1 \approx 14.134725$
- $t_2 \approx 21.022040$
- $t_3 \approx 25.010858$
- $t_4 \approx 30.424876$
- $t_5 \approx 32.935062$