Troubleshooting and diagnostic workflow

Read side by side

On this page

Begin with the failing call, its actual return convention, and the context used by that call. Record the input values and their precisions before changing code or increasing precision. Many apparent arithmetic failures are caused by reading the wrong flags, changing a destination's precision in a way that discards its value, or loading a library different from the one used at compile time.

First distinguish status from rounding

An arithmetic ternary result is negative when the stored result lies below the exact result, zero when exact, and positive when above, subject to the operation's documented special cases. PPF_TERNARY_ERROR is a distinct error sentinel. Therefore a condition such as if (ppf_add(...)) treats ordinary inexact arithmetic as failure. Check the sentinel first, then interpret the ternary relation if needed.

Other interfaces use other conventions. ppf_set_str returns zero on successful complete conversion, while ppf_strtofr returns a ternary relation or its error sentinel and also reports an end pointer. PPFIF import/export use zero for success. Formatting returns a byte count or a negative failure result. Stream text functions return counts. A generic wrapper must preserve these distinctions instead of converting every nonzero integer to an error.

Flags are sticky and describe the context on which they were raised. Clear the relevant flags before an isolated diagnostic call, read them immediately afterward, and preserve them before unrelated output formatting can change another context. A normal return together with PPF_FLAG_ALLOC can describe a successful fallback after a preferred allocation was refused.

Numerical symptoms

Symptom What to examine Practical next step
A correct-looking result is reported as an error Was a nonzero ternary result treated as failure? Compare with PPF_TERNARY_ERROR; report inexactness separately
A value unexpectedly becomes NaN after changing precision Was ppf_set_prec used? Use ppf_prec_round when the old value must be retained and rounded
Increasing an input's precision does not improve the result What is the destination precision, and how were the inputs originally obtained? Increase working and destination precision deliberately; reconstruct inputs from exact source data
Decimal input differs from its decimal spelling Is the value exactly representable in binary at this precision? Inspect inexactness and rounding; use an exact integer/rational source when appropriate
Printing fewer digits seems to change the result Is the discrepancy only in the formatted representation? Compare stored values independently of their display strings
A result overflows or becomes tiny unexpectedly Is the intended context range active? Record emin, emax, and the explicit/default context choice before the call
Flags appear unrelated to the current operation Were earlier sticky flags retained, or was the wrong context queried? Clear and inspect the same context around the operation
A special function returns an error on finite input Is the input admitted, and could the bounded evaluator resolve it? Check the function's domain and construction limits; retain the flags and exact input for diagnosis
Faithful results differ in the last bit Is the comparison expecting a unique nearest result? Check the faithful-rounding contract or choose a specified directed/nearest mode

More precision cannot repair information lost before the value entered the library. Passing a decimal literal through a C double first fixes it to that binary approximation. Reading the original string or constructing the exact rational gives the high-precision calculation the intended starting data. Similarly, a later high-precision destination cannot undo an earlier rounded intermediate. Identify the first irreversible rounding step before adding precision everywhere.

For a suspected numerical defect, reduce the calculation while retaining the same rounding mode, precision, range, and special-value categories. Compare out-of-place and permitted in-place forms when aliasing is involved. If an independent reference is used, match precision and exponent policy on both sides and distinguish a reference disagreement from a proven library defect. Keep the original failing input even if a smaller reproducer is found.

Text, files, and ownership symptoms

Symptom Likely check Practical next step
A parsed field accepts an unwanted suffix Was the prefix parser used without checking endptr? Use ppf_set_str, or validate the suffix explicitly
Text output cannot be read back Base, exponent marker, locale, or too few digits may differ Define the text grammar and locale; use PPFIF for saved objects
ppf_get_str digits look too large by a power of the base The separate exponent may have been ignored Place the radix point before the first digit and apply the returned exponent
A formatted buffer is silently incomplete Was the snprintf required length checked? Treat a nonnegative length at least as large as capacity as truncation
Freeing a returned string crashes Which interface allocated it? Pair ppf_get_str with ppf_free_str, and ppf_asprintf with free
An imported object's precision changes PPFIF restores encoded precision Import into a temporary, then assign into the desired working precision
A small binary record is rejected for resources The encoded precision can require large significand storage Check the 64 MiB default import bounds and the producer's record metadata
Retrying failed input starts at the wrong place Input consumption is not rolled back with the number Reposition a seekable stream or restore an application-owned input buffer

Do not diagnose a saved record solely from its extension. PPFIF has its own identifier, layout, and integrity check. MPFR FPIF, a raw structure dump, and a text number are different representations. A checksum mismatch is a reason to reject a record; removing the check to make an import succeed conceals the problem. Retain a bounded failing record and the producer's identity when investigating interoperability.

Allocation and concurrency symptoms

Symptom What to examine Practical next step
Clearing one number damages another Were live structures copied by assignment or memcpy? Use initialized destinations with ppf_set, or exchange ownership with ppf_swap
A custom allocator fails during cleanup Did its opaque state or pool expire too early? Keep allocator state alive until all dependent objects, strings, and contexts are cleared
A failed resize destroys the original block Does the custom reallocator preserve its input on failure? Correct the callback contract before retrying numerical work
Memory remains after a large calculation Is it an arena, thread workspace, or global cache? Clean the relevant owner at a deliberate lifecycle boundary
Several threads occasionally produce corrupted state Are a context, destination, random state, or raw buffer shared mutably? Give workers private state or protect the whole affected operation with a lock
More workers make a workload slower Are internal teams competing with application workers? Measure one parallel layer first, then compare controlled configurations
Shutdown or library unload crashes Can another thread still call the library or run its destructor? Join workers and release their state before unloading

An allocation flag does not identify every process allocation. If a GMP or system allocation fails outside the recoverable project allocator domain, the ordinary destination-preservation guarantee is not a complete recovery strategy. Likewise, a custom allocator that returns an invalid pointer or reports a false capacity breaks the caller contract. Start fault-injection tests with a simple, correctly accounting allocator and a valid initialized destination, as in the allocation example.

Build and deployment symptoms

Symptom Practical next step
GMP header or library discovery fails Install matching development files and check compiler architecture and search paths
A core-only consumer unexpectedly requests MPFR Use find_package with COMPONENTS core
The adapter is unavailable after installation Build it with official MPFR development headers and request COMPONENTS mpfr in the consumer
A static link has unresolved dependencies Use the exported static target or pkg-config's static dependency flags; remember that selecting an archive is a separate step
A program links but cannot load a shared library Correct its installed runtime-library search configuration
OpenMP configuration or linking fails Select the matching compiler runtime, or build with PPF_OPENMP=OFF
An executable fails with an illegal instruction elsewhere Check native compiler tuning and the target CPU instruction set
Tests or examples are absent from a build Review CMake feature detection and build options, then list configured tests

When a build behaves inconsistently after a compiler or sanitizer change, configure a new directory before investigating numerical code. Run one small installed consumer to distinguish package discovery and loader problems from failures inside a larger application. Capture the actual library path and runtime version, not only the version requested in a build script.

Prepare a useful diagnostic record

A concise reproducer should initialize its own objects and contexts, contain the exact input construction, perform the failing operation, check the result, and clean up. Include the precision of every relevant object, the rounding mode, exponent limits, return value, and flag mask. For a parser problem, include the bytes or a safely bounded file, the base, and the first unconsumed position. For an aliasing problem, state which pointers refer to the same object. For a concurrency problem, identify which resources are shared and the point at which readers finish.

Also record compiler and dependency versions, build options, optional interfaces, worker settings, and whether the failure occurs in a fresh process. Keep the original diagnostic output and distinguish an actual failure from a test that was never executed. A missing executable permission, an unavailable sanitizer runtime, or a timeout is useful evidence, but none is a passing numerical result. These details make a report actionable without requiring another reader to reconstruct the application's entire environment.