Precision

PARI computes with reals to a working precision. LibPARI states that precision in bits everywhere on its own surface, and converts only at the ccall boundary.

The units

PARI's own units are easy to misread, so they are pinned here against the headers shipped with PARI_jll (PARI 2.17):

  • A prototype p or b argument is a bit count. pariinl.h defines prec2nbits(long x) { return x; } — the value is already in bits.
  • PARI allocates a t_REAL to a whole number of machine words, so a request is rounded up: nbits2prec(x) = ceil(x / 64) * 64 on a 64-bit platform.

LibPARI.nbits2prec exposes that rounding:

requested bitsallocated bits
164
464
6464
65128
128128
200256
A historical mistake, now fixed

Before version 0.18.0 every generated binding defaulted to prec = 4, with a comment describing it as "word precision". It was in fact a request for 4 bits, which PARI rounded up to its 64-bit minimum — not a deliberate choice. Bindings now default to LibPARI's working precision.

The default

LibPARI.default_precision is 128 bits, which is GP's own realbitprecision default (about 38 decimal digits — the width PARI prints by default). Matching GP means the same expression gives the same answer in gp and in LibPARI.

Tracking Julia's precision(BigFloat) (256 bits) was the alternative. It was rejected: it would make LibPARI disagree with the library it wraps.

Setting it

setprecision(LibPARI.Gen, 256) do
    LibPARI.PARI.mppi()        # computed to 256 bits
end

Scopes nest and unwind, including when the body throws:

setprecision(LibPARI.Gen, 256) do
    setprecision(LibPARI.Gen, 512) do
        precision(LibPARI.Gen)   # 512
    end
    precision(LibPARI.Gen)       # back to 256
end

precision(LibPARI.Gen) reads the value in force; precision(g::LibPARI.Gen) reads the accuracy of a real value, and raises ArgumentError for an exact one — test with LibPARI.isexact.

What the scope reaches, and what it does not

follows setprecision(Gen, …)
generated bindings taking p or byes — it is their keyword default
Gen^Gen and the hand-written numeric APIyes
conversions into and out of a Genvalue-preserving, so unaffected
gp_evalno — see below
a task spawned inside the scopeno — see below

gp_eval follows PARI's process-global realprecision, not the scope. The GP interpreter reads its own global default, which is shared by every thread in the process; LibPARI does not write it behind your back. Set it from GP itself if you need to:

LibPARI.gp_eval("default(realbitprecision, 512)")

The scope is task-local. It nests and unwinds correctly, and it does cross the hop onto LibPARI's PARI worker tasks — the precision is read in the calling task and carried into the closure that gets marshalled. But a task you spawn inside the scope does not inherit it:

setprecision(LibPARI.Gen, 512) do
    fetch(Threads.@spawn precision(LibPARI.Gen))   # the default, not 512
end

This is a limitation of the Julia 1.10 LTS floor, which has no Base.ScopedValues; a ScopedValue would be inherited. Set the precision inside the spawned task, or revisit when the compat floor moves to 1.11.

Precision and conversion

Gen(x) preserves x exactly, whatever its width:

x = big"1.00000000000000000000000000000001"
BigFloat(LibPARI.Gen(x)) == x        # true

A wide float is decomposed into its exact integer significand and binary exponent and rebuilt at a precision that holds every bit — nothing passes through Cdouble. An IEEEFloat takes PARI's dbltor, which is exact.

Coming back, BigFloat(g) is exact by default: the result carries PARI's own mantissa, at whatever precision that takes, which may exceed the ambient precision(BigFloat). Pass the keyword to round once to a chosen width instead:

BigFloat(g; precision = 256)

Inf, -Inf and NaN raise InexactError: PARI's t_INFINITY exists but does not take part in general arithmetic, so mapping onto it would produce values that fail later, far from the conversion.

Series precision

A series precision (prototype code P) is a number of terms, not a bit count. It has its own default and is deliberately outside the precision scope.

Reference

LibPARI.GenMethod
Gen(x::AbstractFloat) -> Gen

Convert a Julia floating-point number to a Gen (a PARI t_REAL), preserving the value exactly.

An IEEEFloat (Float16, Float32, Float64) goes through PARI's dbltor, which is exact. Any wider float — a BigFloat, or a third-party AbstractFloat — is decomposed into its exact integer significand and binary exponent and rebuilt at a precision that holds every bit, so nothing passes through Cdouble (REQ-PREC-08).

Inf, -Inf and NaN raise InexactError: PARI's t_INFINITY does not take part in general arithmetic (REQ-PROM-10).

Examples

julia> using LibPARI

julia> BigFloat(LibPARI.Gen(big"1.00000000000000000000000000000001")) ==
           big"1.00000000000000000000000000000001"
true
source
Base.MPFR.setprecisionMethod
setprecision(
    f::Function,
    _::Type{Gen},
    bits::Integer
) -> Any

Run f with LibPARI's working precision set to bits, then restore it.

Scopes nest, and unwind even when f throws. The setting is task-local: the calls f makes are marshalled onto LibPARI's PARI worker tasks, and the precision is read in the calling task and carried across that boundary — but a task spawned inside the scope does not inherit it (Julia 1.10 has no ScopedValues).

The scope governs the calls LibPARI itself makes. It does not reach into the GP interpreter: gp_eval follows PARI's process-global realprecision.

Examples

julia> using LibPARI

julia> setprecision(LibPARI.Gen, 256) do
           precision(LibPARI.Gen)
       end
256
source
Base.precisionMethod
precision(g::Gen) -> Int64

The accuracy of a real Gen, in bits.

PARI stores a t_REAL to a whole number of machine words, so the answer is a multiple of the word size. An exact Gen has no finite precision and raises ArgumentError rather than returning an invented number — test with isexact first.

Examples

julia> using LibPARI

julia> precision(LibPARI.Gen(1.5)) >= 64
true
source
Base.precisionMethod
precision(_::Type{Gen}) -> Int64

The working precision in force for the current task, in bits.

Equal to default_precision outside any setprecision(Gen, bits) scope.

Examples

julia> using LibPARI

julia> precision(LibPARI.Gen) == LibPARI.default_precision()
true
source
LibPARI.default_precisionMethod
default_precision() -> Int64

The working precision LibPARI supplies to PARI, in bits, when a caller does not give one.

Defaults to 128 bits — GP's own realbitprecision default — so that an expression evaluates to the same thing in gp and in LibPARI. Change it for a dynamic extent with setprecision(Gen, bits).

Examples

julia> using LibPARI

julia> LibPARI.default_precision()
128
source
LibPARI.isexactMethod
isexact(g::Gen) -> Bool

Whether g is an exact PARI value — an integer, a rational, or any other type PARI stores without a working precision.

Examples

julia> using LibPARI

julia> LibPARI.isexact(LibPARI.Gen(42))
true
source
LibPARI.nbits2precMethod
nbits2prec(bits::Integer) -> Int64

Round a request of bits up to the precision PARI will actually allocate — a whole number of machine words.

This mirrors PARI's own nbits2prec.

Examples

julia> using LibPARI

julia> LibPARI.nbits2prec(65)
128
source
LibPARI.set_global_precision!Method
set_global_precision!(bits::Integer) -> Int64

Set PARI's process-global working precision, in bits — the default the GP interpreter reads.

This is the setting gp_eval follows; the setprecision(Gen, bits) scope deliberately does not touch it. Unlike that scope it is global and shared by every thread and task in the process, so prefer the scope unless you specifically need to change what GP itself does.

Returns the requested bits.

This replaces the 47 zero-argument sd_* bindings the generator used to emit for PARI's Class: default records. Those called C functions declared (const char *, long) with no arguments set, which is undefined behaviour; they are no longer generated (REQ-PREC-13).

Examples

julia> using LibPARI

julia> old = LibPARI.set_global_precision!(256);

julia> precision(LibPARI.gp_eval("Pi")) >= 256
true

julia> LibPARI.set_global_precision!(old);  # always restore: it is global
source