I want to thank all those who answered here. I included your suggestions in a solution that still had some amazing subtleties. Both answers were important.
Here's the code, including lambda implementations for sine and cosine. Getting the difference in the AtomicInteger index for each of them was key:
package math.series; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.DoubleStream; import java.util.stream.IntStream; public class TaylorSeries { public static final int DEFAULT_NUM_TERMS = 10; public static void main(String[] args) { int n = 10000000; double y = 1.0; System.out.println(String.format("pi using %d terms", n)); System.out.println(String.format("%20s %20s %20s %20s", "n", "series", "expected", "error")); double expected = Math.PI; double series = TaylorSeries.pi(0.0, n); double error = expected - series; System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", y, series, expected, error)); n = 50; System.out.println(String.format("exp using %d terms", n)); System.out.println(String.format("%20s %20s %20s %20s", "x", "series", "expected", "error")); for (double x = 0.0; x <= 3.0; x += 0.25) { expected = Math.exp(x); series = TaylorSeries.expLambda(x, n); error = expected - series; System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", x, series, expected, error)); } System.out.println(String.format("sin using %d terms", n)); System.out.println(String.format("%20s %20s %20s %20s", "x", "series", "expected", "error")); for (double x = 0.0; x <= Math.PI; x += Math.PI/20.0) { expected = Math.sin(x); series = TaylorSeries.sinLambda(x, n); error = expected - series; System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", x, series, expected, error)); } System.out.println(String.format("cos using %d terms", n)); System.out.println(String.format("%20s %20s %20s %20s", "x", "series", "expected", "error")); for (double x = 0.0; x <= Math.PI; x += Math.PI/20.0) { expected = Math.cos(x); series = TaylorSeries.cosLambda(x, n); error = expected - series; System.out.println(String.format("%20.16f %20.16f %20.16f %20.6e", x, series, expected, error)); } } public static double exp(double x, int n) { double sum = 1.0; double term = 1.0; for (int i = 1; i <= n; ++i) { term *= x / i; sum += term; } return sum; } public static double pi(double x, int n) { return IntStream.range(0, n) .mapToDouble(i -> 8.0/(4*i+1)/(4*i+3)) .sum(); } public static double expLambda(double x, int n) { final AtomicInteger i = new AtomicInteger(1); return DoubleStream.iterate( 1.0, term -> term*x/i.getAndIncrement() ).limit(n).sum(); } public static double sinLambda(double x, int n) { final AtomicInteger i = new AtomicInteger(0); return DoubleStream.iterate( 0.0, term -> ((i.get() & 1) == 0 ? 1 : -1)*((i.get() == 0) ? x/i.incrementAndGet() : term*x*x/i.incrementAndGet()/i.incrementAndGet()) ).limit(n).sum(); } public static double cosLambda(double x, int n) { final AtomicInteger i = new AtomicInteger(0); return DoubleStream.iterate( 0.0, term -> ((i.get() & 1) == 0 ? 1 : -1)*((i.get() == 0) ? 1.0/i.incrementAndGet() : term*x*x/i.getAndIncrement()/i.getAndIncrement()) ).limit(n).sum(); } }