diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 7c960e5..17dc150 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -31,7 +31,7 @@ jobs: - compiler: clang build_dir: build-clang steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install build dependencies run: | @@ -39,12 +39,12 @@ jobs: sudo apt-get install -y ninja-build ccache clang - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: version: 3.x - name: Setup uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true cache-dependency-glob: | @@ -52,7 +52,7 @@ jobs: uv.lock - name: Cache deps - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ matrix.build_dir }}/_deps key: ${{ runner.os }}-${{ matrix.compiler }}-deps-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} @@ -63,7 +63,7 @@ jobs: run: task bench-heavy:${{ matrix.compiler }} - name: Upload benchmark artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: benchmark-${{ runner.os }}-${{ matrix.compiler }} if-no-files-found: warn @@ -85,7 +85,7 @@ jobs: - compiler: clang-cl build_dir: build-clang-cl steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install LLVM + Ninja (clang-cl) if: matrix.compiler == 'clang-cl' @@ -94,12 +94,12 @@ jobs: choco install llvm ninja -y --no-progress - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: version: 3.x - name: Setup uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true cache-dependency-glob: | @@ -107,7 +107,7 @@ jobs: uv.lock - name: Cache deps - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ matrix.build_dir }}/_deps key: ${{ runner.os }}-${{ matrix.compiler }}-deps-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} @@ -118,7 +118,7 @@ jobs: run: task bench-heavy:${{ matrix.compiler }} - name: Upload benchmark artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: benchmark-${{ runner.os }}-${{ matrix.compiler }} if-no-files-found: warn @@ -136,10 +136,10 @@ jobs: group: gh-pages-deploy cancel-in-progress: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Download benchmark artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: artifacts pattern: benchmark-* diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bf966e2..a73b9f6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,7 +25,7 @@ jobs: group: gh-pages-deploy cancel-in-progress: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 @@ -35,17 +35,17 @@ jobs: sudo apt-get install -y pngquant - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: version: 3.x - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.14" - name: Setup uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v8.2.0 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bdc92db..8a3d0e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,16 +37,16 @@ jobs: cc: clang cxx: clang++ steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - run: | sudo apt-get update -y sudo apt-get install -y ninja-build ccache - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: version: 3.x - name: Cache build dir (${{ matrix.compiler }}) - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ${{ matrix.build_dir }} key: ${{ runner.os }}-${{ matrix.compiler }}-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} @@ -70,7 +70,7 @@ jobs: - compiler: clang-cl build_dir: build-clang-cl steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Install LLVM + Ninja (clang-cl) if: matrix.compiler == 'clang-cl' @@ -78,7 +78,7 @@ jobs: run: | choco install llvm ninja -y --no-progress - - uses: actions/cache@v4 + - uses: actions/cache@v6 with: path: ${{ matrix.build_dir }} key: ${{ runner.os }}-${{ matrix.compiler }}-build-${{ hashFiles('CMakeLists.txt', '**/CMakeLists.txt', '**/*.cmake') }} @@ -86,7 +86,7 @@ jobs: ${{ runner.os }}-${{ matrix.compiler }}-build- - name: Install Task - uses: go-task/setup-task@v1 + uses: go-task/setup-task@v2 with: version: 3.x diff --git a/.gitignore b/.gitignore index 3bbe562..98f7abd 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ ENV/ CMakeUserPresets.json /tmp +/bundled compile_commands.json # Documentation diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index f5c40cb..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Test (gcc)", - "type": "shell", - "command": "task test:gcc", - }, - { - "label": "Test (clang)", - "type": "shell", - "command": "task test:clang", - }, - { - "label": "Test (msvc)", - "type": "shell", - "command": "task test:msvc", - }, - { - "label": "Test (clang-cl)", - "type": "shell", - "command": "task test:clang-cl", - }, - { - "label": "Benchmark (gcc)", - "type": "shell", - "command": "task bench:gcc", - }, - { - "label": "Benchmark (clang)", - "type": "shell", - "command": "task bench:clang", - }, - { - "label": "Benchmark (msvc)", - "type": "shell", - "command": "task bench:msvc", - }, - { - "label": "Benchmark (clang-cl)", - "type": "shell", - "command": "task bench:all", - }, - { - "label": "Generate Documentation", - "type": "shell", - "command": "task docs", - }, - ] -} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..6178b55 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,54 @@ +{ + "format_on_save": "on", + "formatter": "language_server", + "tab_size": 4, + "hard_tabs": false, + "ensure_final_newline_on_save": true, + "remove_trailing_whitespace_on_save": true, + "file_types": { + "YAML": [ + ".clang-format", + ".clangd" + ], + }, + "file_scan_exclusions": [ + "**/.git", + "**/.DS_Store", + "build-*", + ".cache", + ".venv" + ], + "lsp": { + "clangd": { + "binary": { + "path": "clangd", + }, + }, + }, + "languages": { + "YAML": { + "tab_size": 2, + }, + "C++": { + "language_servers": [ + "clangd" + ], + "formatter": { + "language_server": { + "name": "clangd", + }, + }, + }, + "Python": { + "language_servers": [ + "basedpyright", + "ruff" + ], + "formatter": { + "language_server": { + "name": "ruff", + }, + }, + }, + }, +} diff --git a/.github/copilot-instructions.md b/AGENTS.md similarity index 89% rename from .github/copilot-instructions.md rename to AGENTS.md index 7808cf2..f1196a2 100644 --- a/.github/copilot-instructions.md +++ b/AGENTS.md @@ -1,34 +1,24 @@ This is a C++ header-only library called "libcpprime", intended for fast primality testing of 64-bit integers. - The library itself and its test code should be written to work with compilers supporting C++11. Benchmark and other temporary files may use the latest C++ features. The library implementation should pay particular attention to compatibility with older compilers. - Supported compilers include gcc, clang, msvc, clang-cl, and the gcc and clang versions within mingw. - -When you want to run tests or benchmarks, execute the tasks described in tasks.json. -For detailed benchmark results, please refer to benchmarks/bench_summary.md. -Running tests and benchmarks takes approximately 20-30 seconds. - +When you want to run tests or benchmarks, execute the tasks described in Taskfile.yml. When optimizing code, primarily use gcc or msvc for benchmarks. However, to avoid significant speed differences between compilers, run them with clang and clang-cl once you have finished the initial implementation. Please inform users of any breaking changes. - .txt files often contain large amounts of data. Do not read files with the .txt extension. - If you wish to generate data mechanically, please create C++ code or Python scripts within the tmp folder. - For primality testing in Python, you can use Scipy. For execution speed, please prioritize using PyPy. - All code and README.md should be written in English. However, this response uses the language currently used in our chat. The directory structure is as follows: diff --git a/CMakeLists.txt b/CMakeLists.txt index cbd31d1..8cd319b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,7 +146,7 @@ if(LIBCPPRIME_BUILD_TESTS) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.12.1 + GIT_TAG v1.17.0 ) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) @@ -207,15 +207,7 @@ if(LIBCPPRIME_BUILD_BENCHMARKS) add_executable(benchmark_isprime benchmarks/isprime_bench.cpp) target_compile_features(benchmark_isprime PRIVATE cxx_std_23) - include(FetchContent) - FetchContent_Declare( - nanobench - GIT_REPOSITORY https://github.com/martinus/nanobench.git - GIT_TAG v4.3.11 - ) - FetchContent_MakeAvailable(nanobench) - - target_link_libraries(benchmark_isprime PRIVATE libcpprime nanobench::nanobench) + target_link_libraries(benchmark_isprime PRIVATE libcpprime) if(LIBCPPRIME_MSVC_LIKE) target_compile_options(benchmark_isprime PRIVATE /W4 /permissive- /Zc:__cplusplus) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1420f3b..ae46259 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ Thanks for your interest in improving `libcpprime`. ### Python tooling (benchmarks/docs) -- Python >= 3.12 +- Python >= 3.14 - `uv` - pngquant @@ -52,7 +52,7 @@ cd libcpprime ### 2) Install dependencies - Install CMake, compiler(s), and Task. -- For docs/benchmark plots, install Python 3.12+ and `uv`. +- For docs/benchmark plots, install Python 3.14+ and `uv`. ### 3) Run tests @@ -63,7 +63,7 @@ task test:msvc task test:clang-cl ``` -Typical runtime for tests is around 20-60 seconds per run. +Typical runtime for tests is around 10 seconds per run. ## Command reference diff --git a/LICENSE b/LICENSE index 51d25a4..d66dcf8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Rac75116 +Copyright (c) 2026 sortA Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,20 @@ SOFTWARE. --- +The algorithm in this library is based on Bradley Berg's method. + +Copyright 2018 Bradley Berg < (My last name) @ t e c h n e o n . c o m > + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +This algorithm is deliberately unpatented. The license above also +lets you even freely use it in commercial code. +Primality testing using a hash table of bases originated with Steven Worley. + +--- + This software includes portions derived from libdivide (https://libdivide.com) Copyright (C) 2010 - 2019 ridiculous_fish, diff --git a/README.md b/README.md index 4aeec18..e5a74be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # libcpprime -![badge](https://github.com/Rac75116/libcpprime/actions/workflows/tests.yml/badge.svg) +![badge](https://github.com/sortA0329/libcpprime/actions/workflows/tests.yml/badge.svg) **libcpprime** is an efficient C++ implementation of a primality test optimized for 64-bit integers. @@ -98,51 +98,56 @@ g++ -I ./libcpprime -O3 Main.cpp Benchmarks are executed on GitHub Actions. -Workflow: [bench.yml](https://github.com/Rac75116/libcpprime/actions/workflows/bench.yml) +Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/bench.yml) ### Linux (gcc) -[View Summary](https://rac75116.github.io/libcpprime/benchmarks/latest/benchmark-Linux-gcc/bench_summary.md) +[View Summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Linux-gcc/bench_summary.md)

- Linux gcc summary - Linux gcc IsPrime - Linux gcc IsPrimeNoTable + Linux gcc summary + Linux gcc IsPrime + Linux gcc IsPrimeNoTable

### Linux (clang) -[View summary](https://rac75116.github.io/libcpprime/benchmarks/latest/benchmark-Linux-clang/bench_summary.md) +[View summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Linux-clang/bench_summary.md)

- Linux clang summary - Linux clang IsPrime - Linux clang IsPrimeNoTable + Linux clang summary + Linux clang IsPrime + Linux clang IsPrimeNoTable

### Windows (msvc) -[View Summary](https://rac75116.github.io/libcpprime/benchmarks/latest/benchmark-Windows-msvc/bench_summary.md) +[View Summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Windows-msvc/bench_summary.md)

- Windows msvc summary - Windows msvc IsPrime - Windows msvc IsPrimeNoTable + Windows msvc summary + Windows msvc IsPrime + Windows msvc IsPrimeNoTable

### Windows (clang-cl) -[View Summary](https://rac75116.github.io/libcpprime/benchmarks/latest/benchmark-Windows-clang-cl/bench_summary.md) +[View Summary](https://sortA0329.github.io/libcpprime/benchmarks/latest/benchmark-Windows-clang-cl/bench_summary.md)

- Windows clang-cl summary - Windows clang-cl IsPrime - Windows clang-cl IsPrimeNoTable + Windows clang-cl summary + Windows clang-cl IsPrime + Windows clang-cl IsPrimeNoTable

## Releases +- 2026/06/28 ver 1.3.4 + - Improve performance + - Prevent unnecessary code from being included when `FeatureTestMacros.hpp` is included + - Fix the name and link in the copyright notice + - Add the copyright notice for Bradley Berg's algorithm to the LICENSE file - 2026/02/25 ver 1.3.3 - Update copyright year to 2026 - Add -O3 -march=native to the compilation flags during benchmarking and testing diff --git a/Taskfile.yml b/Taskfile.yml index 986eb83..45be615 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -30,10 +30,9 @@ tasks: -DCMAKE_BUILD_TYPE=Release configure:msvc: - desc: Configure MSVC build (Visual Studio 17 2022) + desc: Configure MSVC build cmds: - cmake -S . -B build-msvc - -G "Visual Studio 17 2022" -DLIBCPPRIME_COMPILER=msvc # ============================ @@ -152,6 +151,13 @@ tasks: - cp README.md docs/index.md - uv run mkdocs serve + # ============================ + # Bundle + # ============================ + bundle: + desc: Bundle public headers by expanding all local includes + cmd: uv run python scripts/bundle_headers.py + # ============================ # Aggregates # ============================ diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index bc48c8c..087eb03 100644 --- a/benchmarks/isprime_bench.cpp +++ b/benchmarks/isprime_bench.cpp @@ -1,10 +1,7 @@ -#include - #include #include #include #include -#include #include #include #include @@ -12,18 +9,44 @@ #include #include #include +#include +#include #include +#include #include +#include + +#if defined(_MSC_VER) + +template +void doNotOptimizeAway(T const& val) { + volatile T sink = val; + (void)sink; +} + +#else + +// see https://github.com/google/benchmark/blob/v1.7.1/include/benchmark/benchmark.h#L443-L446 +template +void doNotOptimizeAway(T const& val) { + asm volatile("" : : "r,m"(val) : "memory"); +} + +template +void doNotOptimizeAway(T& val) { +#if defined(__clang__) + asm volatile("" : "+r,m"(val) : : "memory"); +#else + asm volatile("" : "+m,r"(val) : : "memory"); +#endif +} +#endif int main(int argc, char** argv) { bool heavy = (argc > 1 && std::string(argv[1]) == "--heavy"); - using namespace ankerl::nanobench; - auto start_time = std::chrono::high_resolution_clock::now(); - const char* out_prime = "benchmarks/bench_IsPrime.csv"; - const char* out_notable = "benchmarks/bench_IsPrimeNoTable.csv"; const int samples = heavy ? 64000 : 32000; static std::uint32_t weighted[89440]; @@ -32,18 +55,19 @@ int main(int argc, char** argv) { weighted[count++] = 64 - i; } } - auto bench = [rng = Rng(100), heavy](bool (*func)(std::uint64_t)) mutable { - std::uint32_t k = weighted[rng.bounded(89440)]; + + auto bench = [heavy](std::mt19937_64& rng, std::uniform_int_distribution<>& uniform, bool (*func)(std::uint64_t)) mutable { + std::uint32_t k = weighted[uniform(rng)]; std::uint64_t n = (rng() >> k) | 1; - int iters = (heavy ? 300 : 250); + int iters = (heavy ? 300 : 100); bool is_prime = func(n); auto min_time = std::numeric_limits::max(); for (int trial = 0; trial < (heavy ? 24 : 16); ++trial) { - auto start = Clock::now(); + auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iters; ++i) { doNotOptimizeAway(func(n)); } - auto end = Clock::now(); + auto end = std::chrono::high_resolution_clock::now(); auto time = static_cast(std::chrono::duration_cast(end - start).count()) / iters; if (time < min_time) { min_time = time; @@ -55,16 +79,6 @@ int main(int argc, char** argv) { // Fast I/O settings std::ios::sync_with_stdio(false); - // Write headers - { - std::ofstream f(out_prime, std::ios::binary); - f << "n,is_prime,time_ns\n"; - } - { - std::ofstream f(out_notable, std::ios::binary); - f << "n,is_prime,time_ns\n"; - } - double time_prime_sum[65] = {}; std::int32_t count_prime[65] = {}; double time_composite_sum[65] = {}; @@ -75,12 +89,15 @@ int main(int argc, char** argv) { std::int32_t count_composite_NoTable[65] = {}; // Emit results for cppr::IsPrime - { - for (std::uint32_t i = 0; i < 4096; ++i) bench(&cppr::IsPrime); // warmup - std::ofstream f(out_prime, std::ios::binary | std::ios::app); + auto bench_IsPrime = [&] { + std::mt19937_64 rng(100); + std::uniform_int_distribution<> uniform(0, 89439); + for (std::uint32_t i = 0; i < 4096; ++i) bench(rng, uniform, &cppr::IsPrime); // warmup + std::ofstream f("benchmarks/bench_IsPrime.csv", std::ios::trunc); + f << "n,is_prime,time_ns\n"; f.setf(std::ios::fmtflags(0), std::ios::floatfield); // default for (int i = 0; i < samples; ++i) { - auto [n, isp, t] = bench(&cppr::IsPrime); + auto [n, isp, t] = bench(rng, uniform, &cppr::IsPrime); char buf[96]; int len = std::snprintf(buf, sizeof(buf), "%llu,%d,%.12f\n", static_cast(n), isp ? 1 : 0, t); f.write(buf, len); @@ -93,15 +110,18 @@ int main(int argc, char** argv) { count_composite[bitlen] += 1; } } - } + }; // Emit results for cppr::IsPrimeNoTable - { - for (std::uint32_t i = 0; i < 512; ++i) bench(&cppr::IsPrimeNoTable); // warmup - std::ofstream f(out_notable, std::ios::binary | std::ios::app); + auto bench_IsPrimeNoTable = [&] { + std::mt19937_64 rng(100); + std::uniform_int_distribution<> uniform(0, 89439); + for (std::uint32_t i = 0; i < 512; ++i) bench(rng, uniform, &cppr::IsPrimeNoTable); // warmup + std::ofstream f("benchmarks/bench_IsPrimeNoTable.csv", std::ios::trunc); + f << "n,is_prime,time_ns\n"; f.setf(std::ios::fmtflags(0), std::ios::floatfield); // default for (int i = 0; i < samples; ++i) { - auto [n, isp, t] = bench(&cppr::IsPrimeNoTable); + auto [n, isp, t] = bench(rng, uniform, &cppr::IsPrimeNoTable); char buf[96]; int len = std::snprintf(buf, sizeof(buf), "%llu,%d,%.12f\n", static_cast(n), isp ? 1 : 0, t); f.write(buf, len); @@ -114,43 +134,85 @@ int main(int argc, char** argv) { count_composite_NoTable[bitlen] += 1; } } + }; + + if (heavy) { + bench_IsPrime(); + bench_IsPrimeNoTable(); + } else { + auto th1 = std::thread(bench_IsPrime); + auto th2 = std::thread(bench_IsPrimeNoTable); + th1.join(); + th2.join(); } // Output summary - if (std::filesystem::exists("benchmarks/bench_summary.md")) { - std::filesystem::copy_file("benchmarks/bench_summary.md", "benchmarks/bench_summary_prev.md", std::filesystem::copy_options::overwrite_existing); - } - std::ofstream f_summary("benchmarks/bench_summary.csv", std::ios::trunc); - std::ofstream f_summary_md("benchmarks/bench_summary.md", std::ios::trunc); - f_summary << "avg_time_prime_IsPrime,avg_time_prime_IsPrimeNoTable,avg_time_composite_IsPrime,avg_time_composite_IsPrimeNoTable\n"; - f_summary_md << "| Bit Width | IsPrime Avg Time (ns, prime) | IsPrimeNoTable Avg Time (ns, prime) | IsPrime Avg Time (ns, composite) | IsPrimeNoTable Avg Time (ns, composite) |\n"; - f_summary_md << "|-----------|------------------------------|-------------------------------------|----------------------------------|-----------------------------------------|\n"; - f_summary << std::fixed << std::setprecision(6); - f_summary_md << std::fixed << std::setprecision(2); - for (std::int32_t i = 1; i <= 64; ++i) { - auto print_result = [](std::ofstream& f, double val, std::int32_t count) -> std::ofstream& { - if (count) { - f << (val / count); - } else { - f << "nan"; + std::ofstream summary("benchmarks/bench_summary.csv", std::ios::trunc); + std::ostringstream summary_md; + summary << "avg_time_prime_IsPrime,avg_time_prime_IsPrimeNoTable,avg_time_composite_IsPrime,avg_time_composite_IsPrimeNoTable\n"; + summary << std::fixed << std::setprecision(6); + summary_md << std::fixed << std::setprecision(2); + auto print_result = [](auto& f, double val, std::int32_t count) -> auto& { + if (count) { + f << (val / count); + } else { + f << "nan"; + } + return f; + }; + auto average_or_nan = [](double sum, std::int32_t count) -> double { + if (count) { + return sum / count; + } + return std::numeric_limits::quiet_NaN(); + }; + auto range_average = [&](std::int32_t begin, std::int32_t end, const double* sums, const std::int32_t* counts) -> double { + double total = 0.0; + std::int32_t used = 0; + for (std::int32_t i = begin; i <= end; ++i) { + double avg = average_or_nan(sums[i], counts[i]); + if (avg == avg) { + total += avg; + ++used; } - return f; - }; - print_result(f_summary, time_prime_sum[i], count_prime[i]) << ","; - print_result(f_summary, time_prime_sum_NoTable[i], count_prime_NoTable[i]) << ","; - print_result(f_summary, time_composite_sum[i], count_composite[i]) << ","; - print_result(f_summary, time_composite_sum_NoTable[i], count_composite_NoTable[i]) << "\n"; - f_summary_md << "| " << i << " | "; - print_result(f_summary_md, time_prime_sum[i], count_prime[i]) << " | "; - print_result(f_summary_md, time_prime_sum_NoTable[i], count_prime_NoTable[i]) << " | "; - print_result(f_summary_md, time_composite_sum[i], count_composite[i]) << " | "; - print_result(f_summary_md, time_composite_sum_NoTable[i], count_composite_NoTable[i]) << " |\n"; + } + return used ? (total / used) : std::numeric_limits::quiet_NaN(); + }; + for (std::int32_t i = 1; i <= 64; ++i) { + print_result(summary, time_prime_sum[i], count_prime[i]) << ","; + print_result(summary, time_prime_sum_NoTable[i], count_prime_NoTable[i]) << ","; + print_result(summary, time_composite_sum[i], count_composite[i]) << ","; + print_result(summary, time_composite_sum_NoTable[i], count_composite_NoTable[i]) << "\n"; + } + summary_md << "# Benchmark Summary\n\n"; + summary_md << "## Overall summary\n\n"; + summary_md << "- IsPrime averages " << range_average(1, 64, time_prime_sum, count_prime) << " ns on prime inputs and " << range_average(1, 64, time_composite_sum, count_composite) + << " ns on composite inputs.\n"; + summary_md << "- IsPrimeNoTable averages " << range_average(1, 64, time_prime_sum_NoTable, count_prime_NoTable) << " ns on prime inputs and " + << range_average(1, 64, time_composite_sum_NoTable, count_composite_NoTable) << " ns on composite inputs.\n\n"; + summary_md << "## Averages by 8-bit range (nanoseconds)\n\n"; + summary_md << "| Bit range | IsPrime (prime) | IsPrimeNoTable (prime) | IsPrime (composite) | IsPrimeNoTable (composite) |\n"; + summary_md << "|-----------|-----------------|------------------------|---------------------|----------------------------|\n"; + const std::pair ranges[] = { + {1, 8}, {9, 16}, {17, 24}, {25, 32}, {33, 40}, {41, 48}, {49, 56}, {57, 62}, {63, 64}, + }; + for (std::size_t idx = 0; idx < sizeof(ranges) / sizeof(ranges[0]); ++idx) { + const std::int32_t begin = ranges[idx].first; + const std::int32_t end = ranges[idx].second; + summary_md << "| " << begin << "-" << end << " | "; + summary_md << range_average(begin, end, time_prime_sum, count_prime) << " | "; + summary_md << range_average(begin, end, time_prime_sum_NoTable, count_prime_NoTable) << " | "; + summary_md << range_average(begin, end, time_composite_sum, count_composite) << " | "; + summary_md << range_average(begin, end, time_composite_sum_NoTable, count_composite_NoTable) << " |\n"; } - f_summary << std::flush; - f_summary_md << std::flush; + summary << std::flush; + + std::ofstream summary_md_file("benchmarks/bench_summary.md", std::ios::trunc); + summary_md_file << summary_md.str() << std::flush; std::cout << "Benchmarking completed\n"; - std::cout << "Total time: " << std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() << " seconds\n"; + std::cout << "Total time: " << std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count() << " seconds\n\n"; + std::cout << summary_md.str() << std::flush; return 0; } diff --git a/benchmarks/plot_isprime.py b/benchmarks/plot_isprime.py index a4d9487..28906f5 100644 --- a/benchmarks/plot_isprime.py +++ b/benchmarks/plot_isprime.py @@ -10,7 +10,6 @@ import matplotlib.pyplot as plt import matplotlib.lines as mlines import numpy as np -from PIL import Image file_type = "webp" @@ -90,7 +89,14 @@ def save_scatter( ax.grid(True, linestyle=":", linewidth=0.5, alpha=0.3, which="minor") fig.tight_layout() - fig.savefig(out_path + "." + file_type, format=file_type) + fig.savefig( + out_path + "." + file_type, + format=file_type, + pil_kwargs={ + "optimize": True, + "quality": 60, + }, + ) plt.close(fig) @@ -153,7 +159,14 @@ def save_summary_plots(summary: np.ndarray, out_prefix: str) -> None: ax2.legend(loc="best") fig.tight_layout() - fig.savefig(out_prefix + "." + file_type, format=file_type) + fig.savefig( + out_prefix + "." + file_type, + format=file_type, + pil_kwargs={ + "optimize": True, + "quality": 60, + }, + ) plt.close(fig) @@ -201,15 +214,6 @@ def main() -> int: os.remove(p_notable) os.remove(p_summary) - # Optimize generated images - for fname in [ - "benchmarks/bench_IsPrime." + file_type, - "benchmarks/bench_IsPrimeNoTable." + file_type, - "benchmarks/bench_summary." + file_type, - ]: - with Image.open(fname) as img: - img.save(fname, optimize=True, quality=60) - return 0 diff --git a/include/libcpprime/FeatureTestMacros.hpp b/include/libcpprime/FeatureTestMacros.hpp index e99c129..ab3f0a1 100644 --- a/include/libcpprime/FeatureTestMacros.hpp +++ b/include/libcpprime/FeatureTestMacros.hpp @@ -1,8 +1,8 @@ /** * - * libcpprime https://github.com/Rac75116/libcpprime + * libcpprime https://github.com/sortA0329/libcpprime * - * Copyright (c) 2026 Rac75116 + * Copyright (c) 2026 sortA * SPDX-License-Identifier: MIT * **/ @@ -10,7 +10,7 @@ #ifndef CPPR_INTERNAL_INCLUDED_FEATURE_TEST_MACROS #define CPPR_INTERNAL_INCLUDED_FEATURE_TEST_MACROS -#include "internal/Utils.hpp" +#include "internal/Environment.hpp" #ifdef CPPR_INTERNAL_CONSTEXPR_ENABLED #define CPPR_HAS_CONSTEXPR_IS_PRIME 1 diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index a328730..9e8c2dd 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -1,8 +1,8 @@ /** * - * libcpprime https://github.com/Rac75116/libcpprime + * libcpprime https://github.com/sortA0329/libcpprime * - * Copyright (c) 2026 Rac75116 + * Copyright (c) 2026 sortA * SPDX-License-Identifier: MIT * **/ @@ -10,7 +10,7 @@ * * The algorithm in this library is based on Bradley Berg's method. * See this page for more information: - *https://www.techneon.com/download/is.prime.64.base.data + * https://www.techneon.com/download/is.prime.64.base.data * * Copyright 2018 Bradley Berg < (My last name) @ t e c h n e o n . c o m > * @@ -30,6 +30,7 @@ #include +#include "internal/Environment.hpp" #include "internal/IsPrimeCommon.hpp" #include "internal/Utils.hpp" @@ -53,7 +54,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime49(const std::uint64_t x) noexcept { const std::int32_t S = CountrZero(x - 1); const std::uint64_t D = (x - 1) >> S; const auto one = mint.one(); - const auto mone = mint.neg(one); + const auto mone = mint.mone(); auto c = mint.raw(2); auto d = mint.raw(GetBase(x)); auto a = c; @@ -96,7 +97,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64(const std::uint64_t x) noexcept { const std::int32_t S = CountrZero(x - 1); const std::uint64_t D = (x - 1) >> S; const auto one = mint.one(); - const auto mone = mint.neg(one); + const auto mone = mint.mone(); const std::uint32_t base = GetBase(x); // Third base is packed as a small lookup indexed by the high bits of `base`. const std::uint64_t base_mask = static_cast(15ull | (135ull << 8) | (13ull << 16) | (60ull << 24) | (15ull << 32) | (117ull << 40) | (65ull << 48) | (29ull << 56)); @@ -156,9 +157,7 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrime(std::uint64_t n) noexcept { return internal::IsPrime32(static_cast(n)); } else { // Cheap small-prime screening before the heavier probable-prime tests. - if (internal::TrialDivision64(n)) { - return false; - } + if (internal::TrialDivision64(n)) return false; if (n < (std::uint64_t(1) << 49)) { return internal::IsPrime49(n); } else if (n < (std::uint64_t(1) << 62)) { diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 1ff7aa4..4515e69 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -1,8 +1,8 @@ /** * - * libcpprime https://github.com/Rac75116/libcpprime + * libcpprime https://github.com/sortA0329/libcpprime * - * Copyright (c) 2026 Rac75116 + * Copyright (c) 2026 sortA * SPDX-License-Identifier: MIT * **/ @@ -12,6 +12,7 @@ #include +#include "internal/Environment.hpp" #include "internal/IsPrimeCommon.hpp" #include "internal/Utils.hpp" @@ -26,7 +27,7 @@ constexpr std::uint32_t FlagTable10[32] = { CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime10(const std::uint64_t n) noexcept { return (FlagTable10[n / 32] >> (n % 32)) & 1; } CPPR_INTERNAL_CONSTEXPR_INLINE bool GCDFilter(const std::uint32_t n) noexcept { - auto GCD = [](std::uint32_t x, std::uint32_t y) -> std::uint32_t { + auto GCD = [](std::uint32_t x, std::uint32_t y) CPPR_INTERNAL_INLINE_LAMBDA -> std::uint32_t { // Binary GCD (Stein's algorithm). Assumes y != 0 when x != 0. if (x == 0) return 0; Assume(y != 0); @@ -118,7 +119,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) const std::int32_t S = CountrZero(x - 1); const std::uint64_t D = (x - 1) >> S; const auto one = mint.one(); - const auto mone = mint.neg(one); + const auto mone = mint.mone(); auto test2 = [=](std::uint64_t base1, std::uint64_t base2) -> bool { // Two-base Miller-Rabin using Montgomery arithmetic. auto a = one; @@ -263,38 +264,41 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } } -CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) noexcept { - const MontgomeryModint64Impl mint(x); +template +CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64Base2(const std::uint64_t x, const MontgomeryModint64Impl mint) noexcept { const auto one = mint.one(); - const auto mone = mint.neg(one); - auto miller_rabin_test = [&]() -> bool { - // Baillie-PSW starts with a base-2 Miller-Rabin test. - const std::int32_t S = CountrZero(x - 1); - const std::uint64_t D = (x - 1) >> S; - auto a = one; - auto b = mint.raw(2); - std::uint64_t ex = D; - while (ex != 1) { - auto c = mint.mul(b, b); - if (ex & 1) a = mint.mul(a, b); - b = c; - ex >>= 1; - } - a = mint.mul(a, b); - bool flag = mint.same(a, one) || mint.same(a, mone); - if (x % 4 == 3) return flag; - if (flag) return true; - for (std::int32_t i = 0; i != S - 1; ++i) { - a = mint.mul(a, a); - if (mint.same(a, mone)) return true; - } - return false; - }; - if (!miller_rabin_test()) return false; + const auto mone = mint.mone(); + const std::int32_t S = CountrZero(x - 1); + const std::uint64_t D = (x - 1) >> S; + auto a = one; + auto b = mint.raw(2); + std::uint64_t ex = D; + while (ex != 1) { + auto c = mint.mul(b, b); + if (ex & 1) a = mint.mul(a, b); + b = c; + ex >>= 1; + } + a = mint.mul(a, b); + bool flag = mint.same(a, one) || mint.same(a, mone); + if (x % 4 == 3) return flag; + if (flag) return true; + for (std::int32_t i = 0; i != S - 1; ++i) { + a = mint.mul(a, a); + if (mint.same(a, mone)) return true; + } + return false; +} + +template +CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) noexcept { + const MontgomeryModint64Impl mint(x); + if (!IsPrime64Base2(x, mint)) return false; std::uint64_t D = GetLucasBase(x); if (D <= 1) return D == 1; // Strong Lucas probable prime test (implemented via Lucas sequences in Montgomery domain). const std::uint64_t Q = mint.raw(x - (D - 1) / 4); + const auto one = mint.one(); std::uint64_t u = one; std::uint64_t v = one; std::uint64_t Qn = Q; @@ -302,17 +306,20 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n std::uint64_t k = (x + 1) << CountlZero(x + 1); D = mint.raw(D); std::uint64_t t = (x >> 1) + 1; - for (k <<= 1; k; k <<= 1) { + k <<= 1; + while (k) { std::uint64_t Qt = mint.add(Qn, Qn); Qn = mint.mul(Qn, Qn); u = mint.mul(u, v); v = mint.sub(mint.mul(v, v), Qt); - if (k >> 63) { + std::uint64_t tmp = k; + k <<= 1; + if (tmp >> 63) { Qn = mint.mul(Qn, Q); std::uint64_t uu = u; u = mint.add(u, v); - u = (u >> 1) + ((u & 1) ? t : 0); v = mint.add(mint.mul(D, uu), v); + u = (u >> 1) + ((u & 1) ? t : 0); v = (v >> 1) + ((v & 1) ? t : 0); } } @@ -334,19 +341,25 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { return internal::IsPrime10(n); } else if (n <= 0xffffffff) { if (internal::TrialDivision32(static_cast(n))) return false; - if (n < 39601) { - return internal::GCDFilter(static_cast(n)); - } + if (n < 39601) return internal::GCDFilter(static_cast(n)); return internal::IsPrime32(static_cast(n)); } else { - if (internal::TrialDivision64(n)) { - return false; - } + if (internal::TrialDivision64(n)) return false; +#if defined(_MSC_VER) && !defined(__clang__) if (n < (std::uint64_t(1) << 62)) { return internal::IsPrime64MillerRabin(n); } else { - return internal::IsPrime64BailliePSW(n); + return internal::IsPrime64BailliePSW(n); + } +#else + if (n < 7999252175582851ull) { + return internal::IsPrime64MillerRabin(n); + } else if (n < (std::uint64_t(1) << 62)) { + return internal::IsPrime64BailliePSW(n); + } else { + return internal::IsPrime64BailliePSW(n); } +#endif } } diff --git a/include/libcpprime/internal/Environment.hpp b/include/libcpprime/internal/Environment.hpp new file mode 100644 index 0000000..a5ed017 --- /dev/null +++ b/include/libcpprime/internal/Environment.hpp @@ -0,0 +1,110 @@ +/** + * + * libcpprime https://github.com/sortA0329/libcpprime + * + * Copyright (c) 2026 sortA + * SPDX-License-Identifier: MIT + * + **/ +// This file contains code derived from libdivide +// https://libdivide.com +// +// Original work: +// Copyright (C) 2010 - 2022 ridiculous_fish +// Copyright (C) 2016 - 2022 Kim Walisch +// +// libdivide is dual-licensed under the Boost Software License 1.0 +// and the zlib License. This project uses the Boost Software License 1.0. +// +// The original code has been modified and integrated into this library. +// Modifications include refactoring and API replacement. +// +// Except for the portions derived from libdivide, the remainder of this +// library is licensed under the MIT License. + +#ifndef CPPR_INTERNAL_INCLUDED_INTERNAL_ENVIRONMENT +#define CPPR_INTERNAL_INCLUDED_INTERNAL_ENVIRONMENT + +#include + +#if defined(__has_include) && __has_include() && (!defined(_MSVC_LANG) || _MSVC_LANG >= 202002L) +#include // IWYU pragma: export +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && defined(__cpp_constexpr) && __cpp_constexpr >= 201907L +#define CPPR_INTERNAL_CONSTEXPR_ENABLED +#define CPPR_INTERNAL_CONSTEXPR constexpr +#else +#define CPPR_INTERNAL_CONSTEXPR inline +#endif + +#ifdef __cpp_if_constexpr +#define CPPR_INTERNAL_IF_CONSTEXPR constexpr +#else +#define CPPR_INTERNAL_IF_CONSTEXPR +#endif + +#ifdef __has_builtin +#define CPPR_INTERNAL_HAS_BUILTIN(x) __has_builtin(x) +#else +#define CPPR_INTERNAL_HAS_BUILTIN(x) 0 +#endif + +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC)) +#if !defined(__clang__) || _MSC_VER > 1930 +#include // IWYU pragma: export +#define CPPR_INTERNAL_VC_MUL_INTRINSICS +#pragma intrinsic(_umul128) +#pragma intrinsic(__umulh) +#endif +#if !defined(__clang__) && _MSC_VER >= 1920 +#include // IWYU pragma: export +#define CPPR_INTERNAL_VC_DIV_INTRINSICS +#pragma intrinsic(_udiv128) +#endif +#endif + +#if !defined(__cpp_lib_bitops) && defined(_MSC_VER) && !(CPPR_INTERNAL_HAS_BUILTIN(__builtin_ctzll) && CPPR_INTERNAL_HAS_BUILTIN(__builtin_clzll)) +#include // IWYU pragma: export +#pragma intrinsic(_BitScanForward64) +#pragma intrinsic(_BitScanReverse64) +#endif + +#ifdef __SIZEOF_INT128__ +#define CPPR_INTERNAL_HAS_INT128_T +#if !(defined(__clang__) && defined(_MSC_VER)) +#define CPPR_INTERNAL_HAS_INT128_RUNTIME_DIV +#endif +#endif + +#if defined(__x86_64__) || defined(_M_X64) +#define CPPR_INTERNAL_X86_64 +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define CPPR_INTERNAL_GCC_STYLE_ASM +#endif + +#ifdef __has_attribute +#if __has_attribute(always_inline) +#define CPPR_INTERNAL_INLINE_LAMBDA __attribute__((always_inline)) +#endif +#endif +#ifndef CPPR_INTERNAL_INLINE_LAMBDA +#define CPPR_INTERNAL_INLINE_LAMBDA +#endif + +#ifdef __has_attribute +#if __has_attribute(always_inline) +#define CPPR_INTERNAL_CONSTEXPR_INLINE CPPR_INTERNAL_CONSTEXPR __attribute__((always_inline)) +#endif +#endif +#ifndef CPPR_INTERNAL_CONSTEXPR_INLINE +#ifdef _MSC_VER +#define CPPR_INTERNAL_CONSTEXPR_INLINE CPPR_INTERNAL_CONSTEXPR __forceinline +#else +#define CPPR_INTERNAL_CONSTEXPR_INLINE CPPR_INTERNAL_CONSTEXPR +#endif +#endif + +#endif diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index f22290a..dcd0cbf 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -1,8 +1,8 @@ /** * - * libcpprime https://github.com/Rac75116/libcpprime + * libcpprime https://github.com/sortA0329/libcpprime * - * Copyright (c) 2026 Rac75116 + * Copyright (c) 2026 sortA * SPDX-License-Identifier: MIT * **/ @@ -10,7 +10,7 @@ * * The algorithm in this library is based on Bradley Berg's method. * See this page for more information: - *https://www.techneon.com/download/is.prime.32.base.data + * https://www.techneon.com/download/is.prime.32.base.data * * Copyright 2018 Bradley Berg < (My last name) @ t e c h n e o n . c o m > * @@ -30,6 +30,7 @@ #include +#include "Environment.hpp" #include "Utils.hpp" namespace cppr { @@ -68,7 +69,7 @@ class MontgomeryModint64Impl { } public: - CPPR_INTERNAL_CONSTEXPR MontgomeryModint64Impl(std::uint64_t n) noexcept { + CPPR_INTERNAL_CONSTEXPR_INLINE MontgomeryModint64Impl(std::uint64_t n) noexcept { // Precondition: n is an odd modulus > 2. // Internals: // - rs: R^2 mod n (with R = 2^64) for Montgomery domain conversion @@ -108,13 +109,13 @@ class MontgomeryModint64Impl { return np; } } - CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t neg(std::uint64_t x) const noexcept { + CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t mone() const noexcept { if CPPR_INTERNAL_IF_CONSTEXPR (Strict) { - Assume(x < mod_); - return (mod_ - x) * (x != 0); + Assume(np != 0 && np < mod_); + return mod_ - np; } else { - Assume(x < 2 * mod_); - return (2 * mod_ - x) * (x != 0); + Assume(np != 0 && np < 2 * mod_); + return 2 * mod_ - np; } } CPPR_INTERNAL_CONSTEXPR_INLINE std::uint64_t mul(std::uint64_t x, std::uint64_t y) const noexcept { @@ -175,7 +176,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool TrialDivision32(const std::uint32_t n) noexc constexpr std::uint16_t Bases32[256] = { #include "IsPrimeBases32.txt" }; -CPPR_INTERNAL_CONSTEXPR bool IsPrime32(const std::uint32_t x) noexcept { +CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime32(const std::uint32_t x) noexcept { const std::uint32_t h = x * 0xad625b89; std::uint32_t d = x - 1; std::uint32_t pw = static_cast(Bases32[h >> 24]); @@ -183,7 +184,7 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrime32(const std::uint32_t x) noexcept { d >>= s; if (x < (1u << 21)) { std::uint64_t m = 0xffffffffffffffff / x + 1; - auto mul = [m, x](std::uint32_t a, std::uint32_t b) -> std::uint32_t { return static_cast(Mulu128High(static_cast(a) * b * m, x)); }; + auto mul = [m, x](std::uint32_t a, std::uint32_t b) CPPR_INTERNAL_INLINE_LAMBDA -> std::uint32_t { return static_cast(Mulu128High(static_cast(a) * b * m, x)); }; std::uint32_t cur = pw; if (d != 1) { pw = mul(pw, pw); diff --git a/include/libcpprime/internal/Utils.hpp b/include/libcpprime/internal/Utils.hpp index 222d826..281ecc0 100644 --- a/include/libcpprime/internal/Utils.hpp +++ b/include/libcpprime/internal/Utils.hpp @@ -1,8 +1,8 @@ /** * - * libcpprime https://github.com/Rac75116/libcpprime + * libcpprime https://github.com/sortA0329/libcpprime * - * Copyright (c) 2026 Rac75116 + * Copyright (c) 2026 sortA * SPDX-License-Identifier: MIT * **/ @@ -28,76 +28,7 @@ #include #include -#if defined(__has_include) && __has_include() && (!defined(_MSVC_LANG) || _MSVC_LANG >= 202002L) -#include -#endif - -#if defined(__cpp_lib_is_constant_evaluated) && defined(__cpp_constexpr) && __cpp_constexpr >= 201907L -#define CPPR_INTERNAL_CONSTEXPR_ENABLED -#define CPPR_INTERNAL_CONSTEXPR constexpr -#else -#define CPPR_INTERNAL_CONSTEXPR inline -#endif - -#ifdef __cpp_if_constexpr -#define CPPR_INTERNAL_IF_CONSTEXPR constexpr -#else -#define CPPR_INTERNAL_IF_CONSTEXPR -#endif - -#ifdef __has_builtin -#define CPPR_INTERNAL_HAS_BUILTIN(x) __has_builtin(x) -#else -#define CPPR_INTERNAL_HAS_BUILTIN(x) 0 -#endif - -#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC)) -#if !defined(__clang__) || _MSC_VER > 1930 -#include -#define CPPR_INTERNAL_VC_MUL_INTRINSICS -#pragma intrinsic(_umul128) -#pragma intrinsic(__umulh) -#endif -#if !defined(__clang__) && _MSC_VER >= 1920 -#include -#define CPPR_INTERNAL_VC_DIV_INTRINSICS -#pragma intrinsic(_udiv128) -#endif -#endif - -#if !defined(__cpp_lib_bitops) && defined(_MSC_VER) && !(CPPR_INTERNAL_HAS_BUILTIN(__builtin_ctzll) && CPPR_INTERNAL_HAS_BUILTIN(__builtin_clzll)) -#include -#pragma intrinsic(_BitScanForward64) -#pragma intrinsic(_BitScanReverse64) -#endif - -#ifdef __SIZEOF_INT128__ -#define CPPR_INTERNAL_HAS_INT128_T -#if !(defined(__clang__) && defined(_MSC_VER)) -#define CPPR_INTERNAL_HAS_INT128_RUNTIME_DIV -#endif -#endif - -#if defined(__x86_64__) || defined(_M_X64) -#define CPPR_INTERNAL_X86_64 -#endif - -#if defined(__GNUC__) || defined(__clang__) -#define CPPR_INTERNAL_GCC_STYLE_ASM -#endif - -#ifdef __has_attribute -#if __has_attribute(always_inline) -#define CPPR_INTERNAL_CONSTEXPR_INLINE CPPR_INTERNAL_CONSTEXPR __attribute__((always_inline)) -#endif -#endif -#ifndef CPPR_INTERNAL_CONSTEXPR_INLINE -#ifdef _MSC_VER -#define CPPR_INTERNAL_CONSTEXPR_INLINE CPPR_INTERNAL_CONSTEXPR __forceinline -#else -#define CPPR_INTERNAL_CONSTEXPR_INLINE CPPR_INTERNAL_CONSTEXPR -#endif -#endif +#include "Environment.hpp" namespace cppr { diff --git a/mkdocs.yml b/mkdocs.yml index 5a166e2..9008288 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: libcpprime Documentation -site_url: https://rac75116.github.io/libcpprime/ +site_url: https://sortA0329.github.io/libcpprime/ theme: name: material logo: assets/favicon.png @@ -15,12 +15,12 @@ theme: scheme: slate primary: indigo accent: indigo -repo_url: https://github.com/Rac75116/libcpprime -repo_name: Rac75116/libcpprime -copyright: "Copyright © 2026 Rac75116" +repo_url: https://github.com/sortA0329/libcpprime +repo_name: sortA0329/libcpprime +copyright: "Copyright © 2026 sortA" nav: - libcpprime: index.md - - "GitHub Repository": https://github.com/Rac75116/libcpprime + - "GitHub Repository": https://github.com/sortA0329/libcpprime markdown_extensions: - tables - pymdownx.highlight: diff --git a/pyproject.toml b/pyproject.toml index f771163..52bc118 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,5 @@ dependencies = [ "mkdocs-material[imaging]>=9.7.1", "mkdocs-minify-plugin>=0.8.0", "numpy>=2.3.4", - "pillow>=11.3.0", "pymdown-extensions>=10.19.1", ] diff --git a/scripts/bundle_headers.py b/scripts/bundle_headers.py new file mode 100644 index 0000000..62aa1b8 --- /dev/null +++ b/scripts/bundle_headers.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +Bundle script: for each public header in include/libcpprime/*.hpp, +expand all local #include "..." directives (both .hpp and .txt) +to produce a self-contained file in bundled/. +""" + +import os +import re +import sys + +LOCAL_INCLUDE_RE = re.compile(r'#\s*include\s+"([^"]+)"') + +CPPR_LICENSE = re.compile( + r'/\*\*\n(?: \*[^\n]*\n)*?' + r' \* libcpprime https://github\.com/sortA0329/libcpprime\n' + r'(?: \*[^\n]*\n)*? \*\*/\n?' +) + +LIBDIVIDE_LICENSE = re.compile( + r'// This file contains code derived from libdivide\n' + r'(?://[^\n]*\n)*?' + r'// library is licensed under the MIT License\.\n?' +) + +IWYU_PRAGMA_RE = re.compile(r"\s*// IWYU pragma: export") + + +def strip_cppr_license(text): + return CPPR_LICENSE.sub("", text, count=1) + + +def strip_iwyu_pragma(text): + return IWYU_PRAGMA_RE.sub("", text) + + +def deduplicate_libdivide_license(text): + seen = [False] + + def replace(match): + if seen[0]: + return "" + seen[0] = True + return match.group(0) + + return LIBDIVIDE_LICENSE.sub(replace, text) + + +def bundle_file(filepath, processed, strip_license=False): + abs_path = os.path.normpath(filepath) + base_dir = os.path.dirname(abs_path) + + with open(abs_path, "r") as f: + content = f.read() + + content = strip_iwyu_pragma(content) + + if strip_license: + content = strip_cppr_license(content) + + def replace_include(match): + include_path = match.group(1) + resolved = os.path.normpath(os.path.join(base_dir, include_path)) + + if not os.path.exists(resolved): + print( + f"Warning: {include_path} not found (resolved to {resolved})", + file=sys.stderr, + ) + return match.group(0) + + resolved = os.path.normpath(resolved) + + if resolved in processed: + return "" + + processed.add(resolved) + return bundle_file(resolved, processed, strip_license=True) + + result = LOCAL_INCLUDE_RE.sub(replace_include, content) + return deduplicate_libdivide_license(result) + + +def main(): + script_dir = os.path.dirname(os.path.abspath(__file__)) + repo_root = os.path.normpath(os.path.join(script_dir, "..")) + include_dir = os.path.join(repo_root, "include", "libcpprime") + output_dir = os.path.join(repo_root, "bundled") + + os.makedirs(output_dir, exist_ok=True) + + hpp_files = sorted( + [ + f + for f in os.listdir(include_dir) + if f.endswith(".hpp") and os.path.isfile(os.path.join(include_dir, f)) + ] + ) + + for hpp_file in hpp_files: + filepath = os.path.join(include_dir, hpp_file) + print(f"Bundling {hpp_file}...") + + processed = set() + processed.add(os.path.normpath(filepath)) + bundled = bundle_file(filepath, processed) + + output_path = os.path.join(output_dir, hpp_file) + with open(output_path, "w") as f: + f.write(bundled) + + print(f" -> {output_path}") + + print("Done.") + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index f13fae0..24e61a8 100644 --- a/uv.lock +++ b/uv.lock @@ -327,7 +327,6 @@ dependencies = [ { name = "mkdocs-material", extra = ["imaging"] }, { name = "mkdocs-minify-plugin" }, { name = "numpy" }, - { name = "pillow" }, { name = "pymdown-extensions" }, ] @@ -338,7 +337,6 @@ requires-dist = [ { name = "mkdocs-material", extras = ["imaging"], specifier = ">=9.7.1" }, { name = "mkdocs-minify-plugin", specifier = ">=0.8.0" }, { name = "numpy", specifier = ">=2.3.4" }, - { name = "pillow", specifier = ">=11.3.0" }, { name = "pymdown-extensions", specifier = ">=10.19.1" }, ]