From 9b11a2ceb2a181a6a129d6c979a31f59313fbe89 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 17 Apr 2026 20:43:52 +0900 Subject: [PATCH 01/32] Update README.md to reflect correct repository links and images --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4aeec18..ebdf7a6 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,46 +98,46 @@ 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

From 754a008be5b0eadf23f0d747691c3629cb8584f0 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 17 Apr 2026 20:45:42 +0900 Subject: [PATCH 02/32] Move copilot-instructions.md to AGENTS.md --- .github/copilot-instructions.md => AGENTS.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/copilot-instructions.md => AGENTS.md (100%) diff --git a/.github/copilot-instructions.md b/AGENTS.md similarity index 100% rename from .github/copilot-instructions.md rename to AGENTS.md From 6215992cd2325504080b56424fc66cbccd020bd8 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 17 Apr 2026 20:48:22 +0900 Subject: [PATCH 03/32] Update copyright and repository links to reflect correct name --- LICENSE | 2 +- include/libcpprime/FeatureTestMacros.hpp | 4 ++-- include/libcpprime/IsPrime.hpp | 4 ++-- include/libcpprime/IsPrimeNoTable.hpp | 4 ++-- include/libcpprime/internal/IsPrimeCommon.hpp | 4 ++-- include/libcpprime/internal/Utils.hpp | 4 ++-- mkdocs.yml | 10 +++++----- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/LICENSE b/LICENSE index 51d25a4..435678a 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 diff --git a/include/libcpprime/FeatureTestMacros.hpp b/include/libcpprime/FeatureTestMacros.hpp index e99c129..6a15f55 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 * **/ diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index a328730..9825e39 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 * **/ diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 1ff7aa4..dc8e980 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 * **/ diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index f22290a..94d8313 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 * **/ diff --git a/include/libcpprime/internal/Utils.hpp b/include/libcpprime/internal/Utils.hpp index 222d826..602288f 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 * **/ 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: From 4e19e3a7d388eac8d90688da95a359f2910edc3e Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 17 Apr 2026 21:17:17 +0900 Subject: [PATCH 04/32] Refactor benchmark summary output to include overall averages and detailed 8-bit range statistics --- benchmarks/isprime_bench.cpp | 58 ++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index bc48c8c..14b4386 100644 --- a/benchmarks/isprime_bench.cpp +++ b/benchmarks/isprime_bench.cpp @@ -123,28 +123,56 @@ int main(int argc, char** argv) { 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"; + auto print_result = [](std::ofstream& f, double val, std::int32_t count) -> std::ofstream& { + 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; - }; + } + return used ? (total / used) : std::numeric_limits::quiet_NaN(); + }; + for (std::int32_t i = 1; i <= 64; ++i) { 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"; + } + f_summary_md << "# Benchmark Summary\n\n"; + f_summary_md << "## Overall summary\n\n"; + f_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"; + f_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"; + f_summary_md << "## Averages by 8-bit range\n\n"; + f_summary_md << "| Bit range | 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"; + for (std::int32_t begin = 1; begin <= 64; begin += 8) { + std::int32_t end = begin + 7; + f_summary_md << "| " << begin << "-" << end << " | "; + f_summary_md << range_average(begin, end, time_prime_sum, count_prime) << " | "; + f_summary_md << range_average(begin, end, time_prime_sum_NoTable, count_prime_NoTable) << " | "; + f_summary_md << range_average(begin, end, time_composite_sum, count_composite) << " | "; + f_summary_md << range_average(begin, end, time_composite_sum_NoTable, count_composite_NoTable) << " |\n"; } f_summary << std::flush; f_summary_md << std::flush; From d932b8530b728e9e0bcccd434911592f70b61816 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 17 Apr 2026 22:53:20 +0900 Subject: [PATCH 05/32] Refactor benchmark summary --- benchmarks/isprime_bench.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index 14b4386..6ab140a 100644 --- a/benchmarks/isprime_bench.cpp +++ b/benchmarks/isprime_bench.cpp @@ -14,6 +14,7 @@ #include #include #include +#include int main(int argc, char** argv) { bool heavy = (argc > 1 && std::string(argv[1]) == "--heavy"); @@ -166,8 +167,12 @@ int main(int argc, char** argv) { f_summary_md << "## Averages by 8-bit range\n\n"; f_summary_md << "| Bit range | 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"; - for (std::int32_t begin = 1; begin <= 64; begin += 8) { - std::int32_t end = begin + 7; + 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; f_summary_md << "| " << begin << "-" << end << " | "; f_summary_md << range_average(begin, end, time_prime_sum, count_prime) << " | "; f_summary_md << range_average(begin, end, time_prime_sum_NoTable, count_prime_NoTable) << " | "; From 50c7ec1fba31fa8f16ee7128e91ac4829478fcd2 Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 21 Jun 2026 21:43:57 +0900 Subject: [PATCH 06/32] Update dependencies and improve benchmark output formatting --- CMakeLists.txt | 12 +---- Taskfile.yml | 2 +- benchmarks/isprime_bench.cpp | 99 ++++++++++++++++++++++-------------- 3 files changed, 63 insertions(+), 50 deletions(-) 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/Taskfile.yml b/Taskfile.yml index 986eb83..aadea0f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -30,7 +30,7 @@ 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" diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index 6ab140a..53fc4d0 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,15 +9,38 @@ #include #include #include +#include +#include #include #include #include +#if defined(_MSC_VER) + +template +void doNotOptimizeAway(T const& val); + +#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"; @@ -33,18 +53,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 = [rng = std::mt19937_64(100), uniform = std::uniform_int_distribution(0, 89439), heavy](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); 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; @@ -118,15 +139,12 @@ int main(int argc, char** argv) { } // 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 << std::fixed << std::setprecision(6); - f_summary_md << std::fixed << std::setprecision(2); - auto print_result = [](std::ofstream& f, double val, std::int32_t count) -> std::ofstream& { + 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 { @@ -153,37 +171,40 @@ int main(int argc, char** argv) { return used ? (total / used) : std::numeric_limits::quiet_NaN(); }; for (std::int32_t i = 1; i <= 64; ++i) { - 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"; + 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"; } - f_summary_md << "# Benchmark Summary\n\n"; - f_summary_md << "## Overall summary\n\n"; - f_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"; - f_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"; - f_summary_md << "## Averages by 8-bit range\n\n"; - f_summary_md << "| Bit range | 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"; + 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; - f_summary_md << "| " << begin << "-" << end << " | "; - f_summary_md << range_average(begin, end, time_prime_sum, count_prime) << " | "; - f_summary_md << range_average(begin, end, time_prime_sum_NoTable, count_prime_NoTable) << " | "; - f_summary_md << range_average(begin, end, time_composite_sum, count_composite) << " | "; - f_summary_md << range_average(begin, end, time_composite_sum_NoTable, count_composite_NoTable) << " |\n"; + 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; } From 2b084f7323f9c0eab94a30f9e4ac8d761d09177d Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 21 Jun 2026 22:00:44 +0900 Subject: [PATCH 07/32] Refactor benchmark output handling --- benchmarks/isprime_bench.cpp | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index 53fc4d0..4219248 100644 --- a/benchmarks/isprime_bench.cpp +++ b/benchmarks/isprime_bench.cpp @@ -43,8 +43,6 @@ int main(int argc, char** argv) { 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]; @@ -54,7 +52,7 @@ int main(int argc, char** argv) { } } - auto bench = [rng = std::mt19937_64(100), uniform = std::uniform_int_distribution(0, 89439), heavy](bool (*func)(std::uint64_t)) mutable { + 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); @@ -77,16 +75,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] = {}; @@ -98,11 +86,14 @@ int main(int argc, char** argv) { // 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); + 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); @@ -115,15 +106,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); + 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); @@ -136,7 +130,7 @@ int main(int argc, char** argv) { count_composite_NoTable[bitlen] += 1; } } - } + }; // Output summary std::ofstream summary("benchmarks/bench_summary.csv", std::ios::trunc); From 1abd969a1fe57d70b28a03f769442c292c2706da Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 21 Jun 2026 22:10:10 +0900 Subject: [PATCH 08/32] Streamline benchmark processes for development --- benchmarks/isprime_bench.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index 4219248..a6783c1 100644 --- a/benchmarks/isprime_bench.cpp +++ b/benchmarks/isprime_bench.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,7 @@ int main(int argc, char** argv) { 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) { @@ -85,7 +86,7 @@ int main(int argc, char** argv) { std::int32_t count_composite_NoTable[65] = {}; // Emit results for cppr::IsPrime - { + 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 @@ -109,7 +110,7 @@ int main(int argc, char** argv) { }; // Emit results for cppr::IsPrimeNoTable - { + 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 @@ -132,6 +133,16 @@ int main(int argc, char** argv) { } }; + 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 std::ofstream summary("benchmarks/bench_summary.csv", std::ios::trunc); std::ostringstream summary_md; From f6c1238dc6ec6be28dab9a3989510862a83a9f8e Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 21 Jun 2026 22:24:07 +0900 Subject: [PATCH 09/32] Accelerate image generation processing for benchmarks --- benchmarks/plot_isprime.py | 28 ++++++++++++++++------------ pyproject.toml | 1 - uv.lock | 2 -- 3 files changed, 16 insertions(+), 15 deletions(-) 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/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/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" }, ] From d3225b70b061ed80f86e1116f8b54a31b715e524 Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 21 Jun 2026 22:34:00 +0900 Subject: [PATCH 10/32] Update AGENTS.md to correct task file reference and streamline instructions --- AGENTS.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7808cf2..f1196a2 100644 --- a/AGENTS.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: From 9d92ed2e6ca51fd18183cb583ce8aa41bff6cc3a Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 21 Jun 2026 22:38:30 +0900 Subject: [PATCH 11/32] Fix errors in CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From cf481d265e348565ea438edfe48349516315d0b9 Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 22 Jun 2026 21:29:08 +0900 Subject: [PATCH 12/32] Fixed a bug in the benchmark for MSVC --- benchmarks/isprime_bench.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benchmarks/isprime_bench.cpp b/benchmarks/isprime_bench.cpp index a6783c1..087eb03 100644 --- a/benchmarks/isprime_bench.cpp +++ b/benchmarks/isprime_bench.cpp @@ -19,7 +19,10 @@ #if defined(_MSC_VER) template -void doNotOptimizeAway(T const& val); +void doNotOptimizeAway(T const& val) { + volatile T sink = val; + (void)sink; +} #else From 7c6627db467de0a5b1089734ed66d2bb67d821e8 Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 22 Jun 2026 22:11:55 +0900 Subject: [PATCH 13/32] Add bundling task and script for public headers --- .gitignore | 1 + Taskfile.yml | 7 +++ scripts/bundle_headers.py | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 scripts/bundle_headers.py 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/Taskfile.yml b/Taskfile.yml index aadea0f..5c3f98d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -152,6 +152,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/scripts/bundle_headers.py b/scripts/bundle_headers.py new file mode 100644 index 0000000..823280b --- /dev/null +++ b/scripts/bundle_headers.py @@ -0,0 +1,90 @@ +#!/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' + r' \*\n' + r' \* libcpprime https://github\.com/sortA0329/libcpprime\n' + r' \*\n' + r' \* Copyright \(c\) 2026 sortA\n' + r' \* SPDX-License-Identifier: MIT\n' + r' \*\n' + r' \*\*/\n?' +) + + +def strip_cppr_license(text): + return CPPR_LICENSE.sub('', text, count=1) + + +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() + + 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) + + return LOCAL_INCLUDE_RE.sub(replace_include, content) + + +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() From 32b02ce2c47b613b5a4d315a5fbb9c03236b02c8 Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 22 Jun 2026 22:40:27 +0900 Subject: [PATCH 14/32] Add inline lambda attribute for optimization --- include/libcpprime/IsPrimeNoTable.hpp | 6 +++--- include/libcpprime/internal/IsPrimeCommon.hpp | 2 +- include/libcpprime/internal/Utils.hpp | 9 +++++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index dc8e980..5edd872 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -26,7 +26,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); @@ -194,7 +194,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } return true; }; - auto test4 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3, std::uint64_t base4) -> bool { + auto test4 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3, std::uint64_t base4) CPPR_INTERNAL_INLINE_LAMBDA -> bool { // Four-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; @@ -267,7 +267,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n const MontgomeryModint64Impl mint(x); const auto one = mint.one(); const auto mone = mint.neg(one); - auto miller_rabin_test = [&]() -> bool { + auto miller_rabin_test = [&]() CPPR_INTERNAL_INLINE_LAMBDA -> 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; diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index 94d8313..2276912 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -183,7 +183,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 602288f..3fc83bc 100644 --- a/include/libcpprime/internal/Utils.hpp +++ b/include/libcpprime/internal/Utils.hpp @@ -86,6 +86,15 @@ #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)) From 92f798a0374de8255a2e3c378a85c94fee10a9b7 Mon Sep 17 00:00:00 2001 From: sortA Date: Mon, 22 Jun 2026 22:59:58 +0900 Subject: [PATCH 15/32] Performance optimization --- include/libcpprime/IsPrime.hpp | 2 +- include/libcpprime/IsPrimeNoTable.hpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index 9825e39..c5da901 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -149,7 +149,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64(const std::uint64_t x) noexcept { } // namespace internal CPPR_INTERNAL_CONSTEXPR bool IsPrime(std::uint64_t n) noexcept { - if (n < 131072) { + if (n < 131072) [[likely]] { return internal::IsPrime17(n); } else if (n <= 0xffffffff) { if (internal::TrialDivision32(static_cast(n))) return false; diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 5edd872..07f9bd8 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -302,12 +302,15 @@ 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); @@ -330,7 +333,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n } // namespace internal CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { - if (n < 1024) { + if (n < 1024) [[likely]] { return internal::IsPrime10(n); } else if (n <= 0xffffffff) { if (internal::TrialDivision32(static_cast(n))) return false; From b0e4ee7befde6b226c290289ba69a417b95a8641 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 09:42:25 +0900 Subject: [PATCH 16/32] Separate macro definitions into Environment.hpp --- include/libcpprime/FeatureTestMacros.hpp | 2 +- include/libcpprime/IsPrime.hpp | 3 +- include/libcpprime/IsPrimeNoTable.hpp | 1 + include/libcpprime/internal/Environment.hpp | 110 ++++++++++++++++++ include/libcpprime/internal/IsPrimeCommon.hpp | 3 +- include/libcpprime/internal/Utils.hpp | 80 +------------ 6 files changed, 117 insertions(+), 82 deletions(-) create mode 100644 include/libcpprime/internal/Environment.hpp diff --git a/include/libcpprime/FeatureTestMacros.hpp b/include/libcpprime/FeatureTestMacros.hpp index 6a15f55..ab3f0a1 100644 --- a/include/libcpprime/FeatureTestMacros.hpp +++ b/include/libcpprime/FeatureTestMacros.hpp @@ -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 c5da901..a0b711a 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -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" diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 07f9bd8..7972c86 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -12,6 +12,7 @@ #include +#include "internal/Environment.hpp" #include "internal/IsPrimeCommon.hpp" #include "internal/Utils.hpp" 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 2276912..2491a78 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -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 { diff --git a/include/libcpprime/internal/Utils.hpp b/include/libcpprime/internal/Utils.hpp index 3fc83bc..281ecc0 100644 --- a/include/libcpprime/internal/Utils.hpp +++ b/include/libcpprime/internal/Utils.hpp @@ -28,85 +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_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 +#include "Environment.hpp" namespace cppr { From 8371ef5d4887861c4366051f749836d267d5c774 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 10:10:34 +0900 Subject: [PATCH 17/32] Improve the quality of the code generated by the bundle command --- scripts/bundle_headers.py | 70 +++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/scripts/bundle_headers.py b/scripts/bundle_headers.py index 823280b..62aa1b8 100644 --- a/scripts/bundle_headers.py +++ b/scripts/bundle_headers.py @@ -12,28 +12,49 @@ LOCAL_INCLUDE_RE = re.compile(r'#\s*include\s+"([^"]+)"') CPPR_LICENSE = re.compile( - r'/\*\*\n' - r' \*\n' + r'/\*\*\n(?: \*[^\n]*\n)*?' r' \* libcpprime https://github\.com/sortA0329/libcpprime\n' - r' \*\n' - r' \* Copyright \(c\) 2026 sortA\n' - r' \* SPDX-License-Identifier: MIT\n' - r' \*\n' - r' \*\*/\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) + 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: + with open(abs_path, "r") as f: content = f.read() + content = strip_iwyu_pragma(content) + if strip_license: content = strip_cppr_license(content) @@ -42,32 +63,39 @@ def replace_include(match): 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) + 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 '' + return "" processed.add(resolved) return bundle_file(resolved, processed, strip_license=True) - return LOCAL_INCLUDE_RE.sub(replace_include, content) + 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') + 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)) - ]) + 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) @@ -78,7 +106,7 @@ def main(): bundled = bundle_file(filepath, processed) output_path = os.path.join(output_dir, hpp_file) - with open(output_path, 'w') as f: + with open(output_path, "w") as f: f.write(bundled) print(f" -> {output_path}") @@ -86,5 +114,5 @@ def main(): print("Done.") -if __name__ == '__main__': +if __name__ == "__main__": main() From 4adf03093147e6751e842abfc5a2905f5eecac2c Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 10:13:17 +0900 Subject: [PATCH 18/32] Update LICENSE to include algorithm attribution --- LICENSE | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/LICENSE b/LICENSE index 435678a..d66dcf8 100644 --- a/LICENSE +++ b/LICENSE @@ -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, From 7ade60ed8f2a02d0439d5d1c7d1e4f9f85fb7e65 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 10:31:22 +0900 Subject: [PATCH 19/32] Add inline attribute for optimization --- include/libcpprime/IsPrimeNoTable.hpp | 4 ++-- include/libcpprime/internal/IsPrimeCommon.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 7972c86..d37ab67 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -120,7 +120,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) const std::uint64_t D = (x - 1) >> S; const auto one = mint.one(); const auto mone = mint.neg(one); - auto test2 = [=](std::uint64_t base1, std::uint64_t base2) -> bool { + auto test2 = [=](std::uint64_t base1, std::uint64_t base2) CPPR_INTERNAL_INLINE_LAMBDA -> bool { // Two-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; @@ -153,7 +153,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } return true; }; - auto test3 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3) -> bool { + auto test3 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3) CPPR_INTERNAL_INLINE_LAMBDA -> bool { // Three-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index 2491a78..61888a7 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -176,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]); From bb7a3d1af92d98fdb1b5e180c0455b3b23d03734 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 10:38:51 +0900 Subject: [PATCH 20/32] Adjusting the code style --- include/libcpprime/IsPrime.hpp | 4 +--- include/libcpprime/IsPrimeNoTable.hpp | 8 ++------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index a0b711a..d90f5af 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -157,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 d37ab67..6264858 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -338,14 +338,10 @@ 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 (n < (std::uint64_t(1) << 62)) { return internal::IsPrime64MillerRabin(n); } else { From fd80ca77f29dd85bb160757ab3fa352bfa53b325 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 10:49:57 +0900 Subject: [PATCH 21/32] Optimization of mint.neg(one) --- include/libcpprime/IsPrime.hpp | 4 ++-- include/libcpprime/IsPrimeNoTable.hpp | 4 ++-- include/libcpprime/internal/IsPrimeCommon.hpp | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index d90f5af..c0b13df 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -54,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; @@ -97,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)); diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 6264858..080e87c 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -119,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) CPPR_INTERNAL_INLINE_LAMBDA -> bool { // Two-base Miller-Rabin using Montgomery arithmetic. auto a = one; @@ -267,7 +267,7 @@ 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); const auto one = mint.one(); - const auto mone = mint.neg(one); + const auto mone = mint.mone(); auto miller_rabin_test = [&]() CPPR_INTERNAL_INLINE_LAMBDA -> bool { // Baillie-PSW starts with a base-2 Miller-Rabin test. const std::int32_t S = CountrZero(x - 1); diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index 61888a7..ffe8e10 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -109,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 { From d81f7e51e2b7f3441492c8c82fd8f69fb2453a12 Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 20:17:41 +0900 Subject: [PATCH 22/32] Remove [[likely]] attribute --- Taskfile.yml | 1 - include/libcpprime/IsPrime.hpp | 2 +- include/libcpprime/IsPrimeNoTable.hpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 5c3f98d..45be615 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -33,7 +33,6 @@ tasks: desc: Configure MSVC build cmds: - cmake -S . -B build-msvc - -G "Visual Studio 17 2022" -DLIBCPPRIME_COMPILER=msvc # ============================ diff --git a/include/libcpprime/IsPrime.hpp b/include/libcpprime/IsPrime.hpp index c0b13df..9e8c2dd 100644 --- a/include/libcpprime/IsPrime.hpp +++ b/include/libcpprime/IsPrime.hpp @@ -150,7 +150,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64(const std::uint64_t x) noexcept { } // namespace internal CPPR_INTERNAL_CONSTEXPR bool IsPrime(std::uint64_t n) noexcept { - if (n < 131072) [[likely]] { + if (n < 131072) { return internal::IsPrime17(n); } else if (n <= 0xffffffff) { if (internal::TrialDivision32(static_cast(n))) return false; diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 080e87c..603bffc 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -334,7 +334,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n } // namespace internal CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { - if (n < 1024) [[likely]] { + if (n < 1024) { return internal::IsPrime10(n); } else if (n <= 0xffffffff) { if (internal::TrialDivision32(static_cast(n))) return false; From 37af21a79a64315219cde5f6b594a2d85f38ac5b Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 20:26:39 +0900 Subject: [PATCH 23/32] Update GitHub Actions workflows to use latest action versions --- .github/workflows/bench.yml | 24 ++++++++++++------------ .github/workflows/docs.yml | 8 ++++---- .github/workflows/tests.yml | 12 ++++++------ 3 files changed, 22 insertions(+), 22 deletions(-) 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..0c8a7f4 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" - 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 From f06d656d9104ef89960dd0e7854bb0bb4ae2b4dc Mon Sep 17 00:00:00 2001 From: sortA Date: Fri, 26 Jun 2026 22:08:05 +0900 Subject: [PATCH 24/32] Remove the inline attribute from the lambda function within IsPrime64MillerRabin --- include/libcpprime/IsPrimeNoTable.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 603bffc..5604ccb 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -120,7 +120,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) const std::uint64_t D = (x - 1) >> S; const auto one = mint.one(); const auto mone = mint.mone(); - auto test2 = [=](std::uint64_t base1, std::uint64_t base2) CPPR_INTERNAL_INLINE_LAMBDA -> bool { + auto test2 = [=](std::uint64_t base1, std::uint64_t base2) -> bool { // Two-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; @@ -153,7 +153,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } return true; }; - auto test3 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3) CPPR_INTERNAL_INLINE_LAMBDA -> bool { + auto test3 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3) -> bool { // Three-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; @@ -195,7 +195,7 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } return true; }; - auto test4 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3, std::uint64_t base4) CPPR_INTERNAL_INLINE_LAMBDA -> bool { + auto test4 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3, std::uint64_t base4) -> bool { // Four-base Miller-Rabin using Montgomery arithmetic. auto a = one; auto b = one; From d21a926d1ad05ae90a6dd9b2425a8e7ec7ab2773 Mon Sep 17 00:00:00 2001 From: sortA Date: Sat, 27 Jun 2026 09:08:03 +0900 Subject: [PATCH 25/32] Improve performance --- include/libcpprime/IsPrimeNoTable.hpp | 130 ++++++------------ include/libcpprime/internal/IsPrimeCommon.hpp | 2 +- 2 files changed, 41 insertions(+), 91 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 5604ccb..583789b 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -195,107 +195,55 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } return true; }; - auto test4 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3, std::uint64_t base4) -> bool { - // Four-base Miller-Rabin using Montgomery arithmetic. - auto a = one; - auto b = one; - auto c = one; - auto d = one; - auto e = mint.build(base1); - auto f = mint.build(base2); - auto g = mint.build(base3); - auto h = mint.build(base4); - std::uint64_t ex = D; - while (ex != 1) { - auto i = mint.mul(e, e); - auto j = mint.mul(f, f); - auto k = mint.mul(g, g); - auto l = mint.mul(h, h); - if (ex & 1) { - a = mint.mul(a, e); - b = mint.mul(b, f); - c = mint.mul(c, g); - d = mint.mul(d, h); - } - e = i; - f = j; - g = k; - h = l; - ex >>= 1; - } - a = mint.mul(a, e); - b = mint.mul(b, f); - c = mint.mul(c, g); - d = mint.mul(d, h); - bool res1 = mint.same(a, one) || mint.same(a, mone); - bool res2 = mint.same(b, one) || mint.same(b, mone); - bool res3 = mint.same(c, one) || mint.same(c, mone); - bool res4 = mint.same(d, one) || mint.same(d, mone); - if (!(res1 && res2 && res3 && res4)) { - for (std::int32_t i = 0; i != S - 1; ++i) { - a = mint.mul(a, a); - b = mint.mul(b, b); - c = mint.mul(c, c); - d = mint.mul(d, d); - res1 |= mint.same(a, mone); - res2 |= mint.same(b, mone); - res3 |= mint.same(c, mone); - res4 |= mint.same(d, mone); - } - if (!res1 || !res2 || !res3 || !res4) return false; - } - return true; - }; // These bases were discovered by Steve Worley and Jim Sinclair. - if (x < 585226005592931977ull) { - if (x < 7999252175582851ull) { - if (x < 350269456337ull) { - return test3(4230279247111683200ull, 14694767155120705706ull, 16641139526367750375ull); - } else if (x < 55245642489451ull) { - return test2(2ull, 141889084524735ull) && test2(1199124725622454117ull, 11096072698276303650ull); - } else { - return test2(2ull, 4130806001517ull) && test3(149795463772692060ull, 186635894390467037ull, 3967304179347715805ull); - } + if (x < 7999252175582851ull) { + if (x < 350269456337ull) { + return test3(4230279247111683200ull, 14694767155120705706ull, 16641139526367750375ull); + } else if (x < 55245642489451ull) { + return test2(2ull, 141889084524735ull) && test2(1199124725622454117ull, 11096072698276303650ull); } else { - return test3(2ull, 123635709730000ull, 9233062284813009ull) && test3(43835965440333360ull, 761179012939631437ull, 1263739024124850375ull); + return test2(2ull, 4130806001517ull) && test3(149795463772692060ull, 186635894390467037ull, 3967304179347715805ull); } } else { - return test3(2ull, 325ull, 9375ull) && test4(28178ull, 450775ull, 9780504ull, 1795265022ull); + return test3(2ull, 123635709730000ull, 9233062284813009ull) && test3(43835965440333360ull, 761179012939631437ull, 1263739024124850375ull); } } -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.mone(); - auto miller_rabin_test = [&]() CPPR_INTERNAL_INLINE_LAMBDA -> 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 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; @@ -342,10 +290,12 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { return internal::IsPrime32(static_cast(n)); } else { if (internal::TrialDivision64(n)) return false; - if (n < (std::uint64_t(1) << 62)) { + if (n < 7999252175582851ull) { return internal::IsPrime64MillerRabin(n); + } else if (n < (std::uint64_t(1) << 62)) { + return internal::IsPrime64BailliePSW(n); } else { - return internal::IsPrime64BailliePSW(n); + return internal::IsPrime64BailliePSW(n); } } } diff --git a/include/libcpprime/internal/IsPrimeCommon.hpp b/include/libcpprime/internal/IsPrimeCommon.hpp index ffe8e10..dcd0cbf 100644 --- a/include/libcpprime/internal/IsPrimeCommon.hpp +++ b/include/libcpprime/internal/IsPrimeCommon.hpp @@ -69,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 From 91148baa75db072464dddfda7c43fdd9a6e858a9 Mon Sep 17 00:00:00 2001 From: sortA Date: Sat, 27 Jun 2026 15:44:52 +0900 Subject: [PATCH 26/32] Improve performance on gcc --- include/libcpprime/IsPrimeNoTable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 583789b..99e57d7 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -263,8 +263,8 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64BailliePSW(const std::uint64_t x) n 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); } } From 831da9d73c921fdf62f8ca04e2cb82decc1a9d8c Mon Sep 17 00:00:00 2001 From: sortA Date: Sat, 27 Jun 2026 23:16:30 +0900 Subject: [PATCH 27/32] Improve performance on msvc --- include/libcpprime/IsPrimeNoTable.hpp | 77 ++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index 99e57d7..d39444f 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -195,17 +195,72 @@ CPPR_INTERNAL_CONSTEXPR_INLINE bool IsPrime64MillerRabin(const std::uint64_t x) } return true; }; + auto test4 = [=](std::uint64_t base1, std::uint64_t base2, std::uint64_t base3, std::uint64_t base4) -> bool { + // Four-base Miller-Rabin using Montgomery arithmetic. + auto a = one; + auto b = one; + auto c = one; + auto d = one; + auto e = mint.build(base1); + auto f = mint.build(base2); + auto g = mint.build(base3); + auto h = mint.build(base4); + std::uint64_t ex = D; + while (ex != 1) { + auto i = mint.mul(e, e); + auto j = mint.mul(f, f); + auto k = mint.mul(g, g); + auto l = mint.mul(h, h); + if (ex & 1) { + a = mint.mul(a, e); + b = mint.mul(b, f); + c = mint.mul(c, g); + d = mint.mul(d, h); + } + e = i; + f = j; + g = k; + h = l; + ex >>= 1; + } + a = mint.mul(a, e); + b = mint.mul(b, f); + c = mint.mul(c, g); + d = mint.mul(d, h); + bool res1 = mint.same(a, one) || mint.same(a, mone); + bool res2 = mint.same(b, one) || mint.same(b, mone); + bool res3 = mint.same(c, one) || mint.same(c, mone); + bool res4 = mint.same(d, one) || mint.same(d, mone); + if (!(res1 && res2 && res3 && res4)) { + for (std::int32_t i = 0; i != S - 1; ++i) { + a = mint.mul(a, a); + b = mint.mul(b, b); + c = mint.mul(c, c); + d = mint.mul(d, d); + res1 |= mint.same(a, mone); + res2 |= mint.same(b, mone); + res3 |= mint.same(c, mone); + res4 |= mint.same(d, mone); + } + if (!res1 || !res2 || !res3 || !res4) return false; + } + return true; + }; // These bases were discovered by Steve Worley and Jim Sinclair. - if (x < 7999252175582851ull) { - if (x < 350269456337ull) { - return test3(4230279247111683200ull, 14694767155120705706ull, 16641139526367750375ull); - } else if (x < 55245642489451ull) { - return test2(2ull, 141889084524735ull) && test2(1199124725622454117ull, 11096072698276303650ull); + if (x < 585226005592931977ull) { + if (x < 7999252175582851ull) { + if (x < 350269456337ull) { + return test3(4230279247111683200ull, 14694767155120705706ull, 16641139526367750375ull); + } else if (x < 55245642489451ull) { + return test2(2ull, 141889084524735ull) && test2(1199124725622454117ull, 11096072698276303650ull); + } else { + return test2(2ull, 4130806001517ull) && test3(149795463772692060ull, 186635894390467037ull, 3967304179347715805ull); + } } else { - return test2(2ull, 4130806001517ull) && test3(149795463772692060ull, 186635894390467037ull, 3967304179347715805ull); + return test3(2ull, 123635709730000ull, 9233062284813009ull) && test3(43835965440333360ull, 761179012939631437ull, 1263739024124850375ull); } } else { - return test3(2ull, 123635709730000ull, 9233062284813009ull) && test3(43835965440333360ull, 761179012939631437ull, 1263739024124850375ull); + return test3(2ull, 325ull, 9375ull) && test4(28178ull, 450775ull, 9780504ull, 1795265022ull); } } @@ -290,6 +345,7 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { return internal::IsPrime32(static_cast(n)); } else { if (internal::TrialDivision64(n)) return false; +#ifndef _MSC_VER if (n < 7999252175582851ull) { return internal::IsPrime64MillerRabin(n); } else if (n < (std::uint64_t(1) << 62)) { @@ -297,6 +353,13 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { } else { return internal::IsPrime64BailliePSW(n); } +#else + if (n < (std::uint64_t(1) << 62)) { + return internal::IsPrime64MillerRabin(n); + } else { + return internal::IsPrime64BailliePSW(n); + } +#endif } } From 834445d60240e26e82ac0e428fc4e18d8b7eade5 Mon Sep 17 00:00:00 2001 From: sortA Date: Sat, 27 Jun 2026 23:19:47 +0900 Subject: [PATCH 28/32] Fix behavior in clang-cl --- include/libcpprime/IsPrimeNoTable.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/libcpprime/IsPrimeNoTable.hpp b/include/libcpprime/IsPrimeNoTable.hpp index d39444f..4515e69 100644 --- a/include/libcpprime/IsPrimeNoTable.hpp +++ b/include/libcpprime/IsPrimeNoTable.hpp @@ -345,17 +345,17 @@ CPPR_INTERNAL_CONSTEXPR bool IsPrimeNoTable(std::uint64_t n) noexcept { return internal::IsPrime32(static_cast(n)); } else { if (internal::TrialDivision64(n)) return false; -#ifndef _MSC_VER - if (n < 7999252175582851ull) { +#if defined(_MSC_VER) && !defined(__clang__) + if (n < (std::uint64_t(1) << 62)) { return internal::IsPrime64MillerRabin(n); - } else if (n < (std::uint64_t(1) << 62)) { - return internal::IsPrime64BailliePSW(n); } else { return internal::IsPrime64BailliePSW(n); } #else - if (n < (std::uint64_t(1) << 62)) { + if (n < 7999252175582851ull) { return internal::IsPrime64MillerRabin(n); + } else if (n < (std::uint64_t(1) << 62)) { + return internal::IsPrime64BailliePSW(n); } else { return internal::IsPrime64BailliePSW(n); } From c0e025561638b2b66f6b6e9ecda62482e7af68ad Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 28 Jun 2026 20:35:20 +0900 Subject: [PATCH 29/32] Updated the Python version for document generation to 3.14 --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0c8a7f4..a73b9f6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,7 +42,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.14" - name: Setup uv uses: astral-sh/setup-uv@v8.2.0 From bbbfda8679ecff34c58a55c1863468930bbd1f8a Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 28 Jun 2026 20:35:32 +0900 Subject: [PATCH 30/32] Delete tasks.json --- .vscode/tasks.json | 50 ---------------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 .vscode/tasks.json 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", - }, - ] -} From a3eb7a1ec4c8cd0ef97180cdf3c24a1c59c67321 Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 28 Jun 2026 20:48:49 +0900 Subject: [PATCH 31/32] Create settings.json for Zed --- .zed/settings.json | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .zed/settings.json 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", + }, + }, + }, + }, +} From 7b65d841cef7617bde70c629b024a102c988496f Mon Sep 17 00:00:00 2001 From: sortA Date: Sun, 28 Jun 2026 20:49:14 +0900 Subject: [PATCH 32/32] Add a changelog to README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ebdf7a6..e5a74be 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,11 @@ Workflow: [bench.yml](https://github.com/sortA0329/libcpprime/actions/workflows/ ## 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