diff --git a/README.md b/README.md index ae035fe..cc5cd40 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Examples include (and will expand to): * RAII and ownership patterns * Parallelism * [parallel-transform](./parallel-transform/) + * [parallel-integral-pi](./parallel-integral-pi/) * Performance‑oriented C++ idioms * STL and ranges: * [ranges-find-in-vector-string](./ranges-find-in-vector-string/) diff --git a/parallel-integral-pi/Makefile b/parallel-integral-pi/Makefile new file mode 100644 index 0000000..172da2f --- /dev/null +++ b/parallel-integral-pi/Makefile @@ -0,0 +1,30 @@ +# pull in shared compiler settings +include ../common.mk + +# per-example flags +# CXXFLAGS += -pthread + +## get it from the folder name +TARGET := $(notdir $(CURDIR)) +SRCS := $(wildcard *.cpp) +OBJS := $(SRCS:.cpp=.o) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $@ $^ + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +run: $(TARGET) + ./$(TARGET) $(ARGS) + +clean: + rm -f $(OBJS) $(TARGET) + +# Delegates to top-level Makefile +check-format: + $(MAKE) -f ../Makefile check-format DIR=$(CURDIR) + +.PHONY: all clean run check-format diff --git a/parallel-integral-pi/main.cpp b/parallel-integral-pi/main.cpp new file mode 100644 index 0000000..bd682a4 --- /dev/null +++ b/parallel-integral-pi/main.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include + +double +f(double x) +{ + return 4.0 / (1.0 + x * x); +} + +int +main(int argc, char* argv[]) +{ + long long iterations = 1000000; + if (argc > 1) { + iterations = std::atoll(argv[1]); + } + int threads = 10; + if (argc > 2) { + threads = std::atoi(argv[2]); + } + + if (iterations <= 0 || threads <= 0) { + std::cerr << "iterations and threads must be positive integers\n"; + return 1; + } + + const double h = 1.0 / static_cast(iterations); + const long long interior_points = iterations - 1; + const int worker_count = static_cast(std::min(threads, iterations)); + const long long points_per_thread = interior_points / worker_count; + const long long remainder = interior_points % worker_count; + + std::vector thread_pool; + thread_pool.reserve(worker_count); + std::vector partial_sums(worker_count, 0.0); + + for (int t = 0; t < worker_count; ++t) { + thread_pool.emplace_back([&, t]() { + const long long thread_index = t; + const long long start = 1 + thread_index * points_per_thread + std::min(thread_index, remainder); + const long long end = start + points_per_thread + (thread_index < remainder ? 1 : 0); + + double local_sum = 0.0; + for (long long i = start; i < end; ++i) { + const double x = h * static_cast(i); + local_sum += 2.0 * f(x); + } + partial_sums[t] = local_sum; + }); + } + + for (auto& thread : thread_pool) { + thread.join(); + } + + // T_n = (h / 2) * (f(0) + f(1) + 2 * sum of interior points). + double sum = f(0.0) + f(1.0); + for (const double partial_sum : partial_sums) { + sum += partial_sum; + } + + const double pi = (h / 2.0) * sum; + std::cout.precision(15); + std::cout << "pi = " << pi << '\n'; +} \ No newline at end of file diff --git a/parallel-integral-pi/tests.sh b/parallel-integral-pi/tests.sh new file mode 100755 index 0000000..11efa4d --- /dev/null +++ b/parallel-integral-pi/tests.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -euo pipefail + +output=$(./parallel-integral-pi 100000 6) +pi=${output#pi = } + +if ! awk -v pi="$pi" 'BEGIN { + expected = atan2(0, -1) + error = pi - expected + if (error < 0) { + error = -error + } + exit !(error < 0.000000001) +}'; then + echo "Test failed: inaccurate approximation: $pi" + exit 1 +fi + +if ./parallel-integral-pi 0 2 >/dev/null 2>&1; then + echo "Test failed: zero iterations should be rejected" + exit 1 +fi + +echo "Test passed: pi = $pi"