C translation conventions
The goal is a faithful port, not a redesign. For reference, see the template at
new.c. For the in-source comments themselves, follow the
Source documentation conventions.
- Filenames. The C source mirrors its Python sibling's stem, swapping the extension:
pNNNN_sK.py→pNNNN_sK.c(same problem number, same indexK);solver compileproduces thepNNNN_sK_cexecutable. Never hand-write or edit the_cartifact — only the.csource. The.cis source, so it must not be executable (chmod -xif one ends up+x); only the compiled_cis. - Include the runner; write only
solve(). Begin the file with/* Solution to Euler Problem <N>: <Title>. */then#include "runner.h". The header (packages/solver/solver/runners/runner.h, found via the-Ithatscripts/c/compile.shadds) suppliesmain(), the--runs=N/--showparsing, the timing loop, the cross-run consistency check, and the result printing — do not redefinemain()or re-implement any of it. Write onlysolve()and its helpers. - Translate faithfully — same algorithm, same complexity. Do not change the approach during
the port. A
.cis "correct" precisely when its algorithm matches the Python sibling's. solvealways returns a string —const char *solve(int argc, char *argv[]), mirroring the Python-> str. Format the answer into a buffer that outlives the call and return it; for a numeric answer:static char answer[32]; snprintf(answer, sizeof answer, "%lld", result); return answer;. For alist/other, build the literal form the recordedanswerparses back as (e.g."[1, 2, 3]"). The returned pointer is owned bysolve()and must stay valid until the next call — a string literal or astaticbuffer, never a per-callmalloc(the runner copies it for its cross-run check); returnNULLto signal failure.main()(fromrunner.h) passes only the problem inputs tosolve()(argv[1]= first input, …).- Argument parsing: prefer the runner helpers from
runner.h—parse_int(argv[i])(plain ints,2**20power notation,_separators; mirrorsrunner.parse_int) andparse_list(argv[i], &count)(a"[1,2,3]"literal → amalloc'dlong longarray youfree()).atoll/atofremain fine for a plainlong long/double. - primesieve: if the Python uses
primesieve, the C must use the C library —#include <primesieve.h>(the compile script auto-adds-lprimesieve). Useful functions:primesieve_nth_prime,primesieve_count_primes, andprimesieve_generate_primes(start, stop, &size, INT_PRIMES)(free the result withprimesieve_free). - Arbitrary-precision arithmetic: Python ints are unbounded and its floats can be widened with
decimal/fractions, so when a result or intermediate overflows C's 64-bitlong long(or__int128), port it with a bignum library rather than silently truncating. The compile script auto-links each from its#include:- GMP —
#include <gmp.h>(-lgmp):mpz_tintegers,mpq_trationals; the default choice for big-integer work. - MPFR —
#include <mpfr.h>(-lmpfr -lgmp): arbitrary-precision floats with correct rounding, for the rare problem needing fractional precision beyonddouble. - OpenSSL BIGNUM —
#include <openssl/bn.h>(-lcrypto): theBIGNUMtype, convenient when the Python leans on modular arithmetic (BN_mod_exp,BN_mod_mul, …). Reach for these only when the Python genuinely needs the range — a value that fits inlong longstays inlong long(it is far faster). Free everything you allocate:mpz_clear/mpz_clears,mpfr_clear/mpfr_clears,BN_free/BN_CTX_free.
- GMP —
- Bignum is also a speed tool, not only a range tool. Reach for GMP even when the values
still fit in
__int128, if the hot path does repeated modular multiply/exponentiation on operands wider than 64 bits — Miller–Rabin,pow(a, d, n), modular-power loops. A hand-rolledunsigned __int128mulmodhas no 256-bit product to hold the intermediate, so it degrades to a bit-by-bit add-and-shift loop (O(bits) per multiply) and loses to Python'spow(a, d, n)(which is itself fast bignum modexp);mpz_powmstays fast. The faithful, fast port keeps a native__int128fast path for moduli ≤ 2⁶⁴ (where the single-instruction product is unbeatable) and routes anything larger through GMP — mirroring exactly what CPython'spow(a, d, n)does. - Resource files: if the Python reads the
file_urlinput, mirror it — call the runner'sget_text_file(src)(declared inrunner.h) with the projecteuler.net file URL; it serves the locally cachedresources/copy (avoiding a re-fetch), returning a heap buffer youfree(). Mirror the empty-file_urlbranch too: when it is"", use the data embedded in the source as a variable instead of reading a file. - Heap ownership cuts the other way for the result.
get_text_fileandparse_listhand backmalloc'd buffers thatsolve()mustfree(); but the stringsolve()returns must not be heap — return a string literal or astaticbuffer, never a per-callmalloc(see above). - Headers:
runner.halready pulls in<stdio.h>,<stdlib.h>,<string.h>,<time.h>, and<unistd.h>; add another header only for something you use beyond those (e.g.<math.h>,<primesieve.h>,<gmp.h>,<mpfr.h>,<openssl/bn.h>). Compilation is-Werror, so anything unused or implicit still fails. - OverflowError guard: if the Python raises
OverflowError(msg)above some threshold, replicate it — printOverflowError: <msg>to stderr and exit with status 1. - No reasonable C equivalent (e.g.
sympy,networkx, or anything beyondnumpy/primesieve/ the bignum libraries above — GMP, MPFR, OpenSSL BIGNUM): do not fabricate a port. Skip that index and tell the user why, rather than emitting a broken translation. - Build and verify:
solver "compile --clean", thensolver "eval all lang=c"; the compiledpNNNN_sK_cmust match every recordedanswer.