Malachite for GMP Users: Rationals
This page maps the functions of GMP’s rational number type, mpq_t, onto
Rational, from
the malachite-q crate (and re-exported at the top level of the umbrella crate
malachite). It follows the organization of the
Rational Number Functions chapter of the
GMP manual, and is a companion to
Malachite for GMP Users: Integers; the mapping index lists
both, along with the pages for other libraries as they are written. The Conventions described there, including the
Allocation discussion, apply here unchanged.
One fact shapes the whole chapter. A GMP rational is only canonical, in lowest terms with a
positive denominator, if you keep it that way: the arithmetic and comparison functions assume
canonical operands, several assignment functions can leave a value non-canonical, and the manual
is dotted with reminders to call mpq_canonicalize before going on. A
Rational is
canonical by construction: it is a sign together with a numerator and denominator that are kept
coprime, with the denominator always positive. Every constructor reduces its input on the way in,
every operation produces a reduced result, and there is no way to build or observe a
non-canonical value. mpq_canonicalize therefore has no counterpart, and none of the manual’s
canonicalization reminders carry over.
Each function falls into one of four categories:
| meaning | |
|---|---|
| ✓ | A Malachite function does the same thing. |
| ≈ | A Malachite function serves the same purpose, but its specification differs. The notes say how. |
| — | No counterpart is needed, either because Rust handles it for you or because it is outside Malachite’s scope. The notes say which. |
| ✗ | Malachite does not fully support this yet, but will in a future version. |
Initialization and Assignment
| GMP | Malachite | |
|---|---|---|
| — | void mpq_canonicalize (mpq_t op) |
|
| ✓ | void mpq_init (mpq_t x) |
Rational::ZERO |
| — | void mpq_inits (mpq_t x, ...) |
|
| — | void mpq_clear (mpq_t x) |
|
| — | void mpq_clears (mpq_t x, ...) |
|
| ✓ | void mpq_set (mpq_t rop, const mpq_t op) |
Clone |
| ✓ | void mpq_set_z (mpq_t rop, const mpz_t op) |
From |
| ✓ | void mpq_set_ui (mpq_t rop, unsigned long int op1, unsigned long int op2) |
from_unsigneds |
| ✓ | void mpq_set_si (mpq_t rop, signed long int op1, unsigned long int op2) |
from_signeds |
| ✗ | int mpq_set_str (mpq_t rop, const char *str, int base) |
FromStr |
| — | void mpq_swap (mpq_t rop1, mpq_t rop2) |
mpq_canonicalize. Nothing to call: as described above, a
Rational is
always canonical. Wherever the manual attaches a canonicalization reminder to a function, to
mpq_set_ui, mpq_set_si, and mpq_set_str here and to mpq_set_num and mpq_set_den later,
the Malachite counterpart reduces its inputs itself.
mpq_init. let x = Rational::ZERO; holds exactly the value mpq_init produces, 0/1. Rational also implements
Default, which returns
zero.
mpq_inits, mpq_clear, mpq_clears. As with the integers, declaration replaces
initialization and going out of scope replaces clearing; see the fuller discussion under
Initializing Integers.
mpq_set. x = y.clone(), or x.clone_from(&y) if x already holds a value whose
allocations are worth reusing. If you do not need the original afterwards, x = y moves it and
copies nothing.
mpq_set_z. Rational::from(z) for an
Integer or a
Natural, by
value or by reference; the by-value form reuses the integer’s storage as the numerator. The
primitive integer types convert the same way, so Rational::from(3u32) covers what GMP would
spell mpq_set_ui(q, 3, 1).
mpq_set_ui, mpq_set_si. Rational::from_unsigneds(n, d) and
Rational::from_signeds(n, d), for any primitive integer width. The GMP caveat that the result
“may have common factors” and must be canonicalized before use does not exist here:
Rational::from_unsigneds(4, 6) is 2/3. A zero denominator panics at the construction site,
where GMP would divide by zero at some later use. For numerators and denominators that are
already big integers there are
from_naturals
and
from_integers,
each with a _ref variant that borrows instead of consuming.
mpq_set_str. Base 10 works through
FromStr:
"22/7".parse::<Rational>(), accepting an integer like "41" or a fraction like "41/152",
just as GMP does. The numerator and denominator need not be in lowest terms; the parse reduces
them. A zero denominator is an Err at parse time, where GMP accepts the string and the zero
surfaces as a division error later. The sign may only appear at the front of the string. The
other bases are the gap: GMP accepts 2 through 62, and Rational does not yet have the
from_string_base that
Natural and
Integer provide
for bases 2 through 36.
mpq_swap. std::mem::swap swaps
any two values of the same type, exchanging the representations without copying any limbs.
Conversion Functions
A short section, and a simpler one than its integer counterpart: since every finite double is
a rational number, the conversions between double and
Rational are
exact in one direction and rounded in the other, never lossy in both.
| GMP | Malachite | |
|---|---|---|
| ≈ | double mpq_get_d (const mpq_t op) |
RoundingFrom, TryFrom |
| ✓ | void mpq_set_d (mpq_t rop, double op) |
TryFrom |
| ✓ | void mpq_set_f (mpq_t rop, const mpf_t op) |
TryFrom |
| ✗ | char * mpq_get_str (char *str, int base, const mpq_t op) |
Display |
mpq_get_d. GMP truncates toward zero, so f64::rounding_from(&q, Down) reproduces it, and
also returns an Ordering saying
which way the rounding went; pass Nearest instead for round-to-nearest.
f64::try_from(&q) succeeds only when the value is exactly representable, and
ConvertibleFrom
asks that question without performing the conversion. At the large end, the IEEE-following
overflow behavior described under
mpz_get_d applies unchanged. The small end is new,
since a rational can also be too small for a double: there, truncation toward zero yields
zero.
mpq_set_d. Rational::try_from(d). The conversion is exact, as in GMP, and exact means
literal: Rational::try_from(0.1f64) is 3602879701896397/36028797018963968, the value the
double holds, not 1/10. If what you want is the simplest rational that rounds back to the
given double, that is
try_from_float_simplest,
which does return 1/10 for 0.1f64. The conversion fails, rather than being undefined, for an
infinity or a NaN.
mpq_set_f. Rational::try_from(f) for a
Float, also
exact, also failing for an infinity or a NaN. As explained under
Assigning Integers, Malachite’s
Float follows
MPFR rather than GMP’s mpf_t, so this row is the analogue rather than the equivalent of
mpq_set_f.
mpq_get_str. For base 10,
Display produces exactly GMP’s
format: "num/den", or just "num" when the denominator is 1, so q.to_string() and
format!("{q}") are direct replacements, returning a
String with no buffer to
size and no allocation to free. Other bases are missing, as with mpq_set_str above: Rational
has no to_string_base yet. The base only affects how the two integers are printed: 22/7 in
base 16 is "16/7". For a representation where the base matters mathematically,
digits
and
to_digits
produce the positional expansion of a rational in any base, with the eventually-repeating
fractional part represented exactly, and
ToSci
renders a rounded decimal form; GMP has no counterpart to either.
Arithmetic Functions
The operators do all of this section. Where GMP writes a result into an rop you supplied,
Malachite returns it; the *Assign traits cover the in-place case; and every operator has
borrowing forms, so &x + &y works without cloning either side. GMP’s preconditions for this
section also take care of themselves: these functions require canonical operands and promise
canonical results, which in Malachite is the invariant, maintained without being asked.
| GMP | Malachite | |
|---|---|---|
| ✓ | void mpq_add (mpq_t sum, const mpq_t addend1, const mpq_t addend2) |
Add, AddAssign |
| ✓ | void mpq_sub (mpq_t difference, const mpq_t minuend, const mpq_t subtrahend) |
Sub, SubAssign |
| ✓ | void mpq_mul (mpq_t product, const mpq_t multiplier, const mpq_t multiplicand) |
Mul, MulAssign |
| ✓ | void mpq_mul_2exp (mpq_t rop, const mpq_t op1, mp_bitcnt_t op2) |
Shl, ShlAssign |
| ✓ | void mpq_div (mpq_t quotient, const mpq_t dividend, const mpq_t divisor) |
Div, DivAssign |
| ✓ | void mpq_div_2exp (mpq_t rop, const mpq_t op1, mp_bitcnt_t op2) |
Shr, ShrAssign |
| ✓ | void mpq_neg (mpq_t negated_operand, const mpq_t operand) |
Neg, NegAssign |
| ✓ | void mpq_abs (mpq_t rop, const mpq_t op) |
Abs, AbsAssign |
| ✓ | void mpq_inv (mpq_t inverted_number, const mpq_t number) |
Reciprocal, ReciprocalAssign |
mpq_mul_2exp, mpq_div_2exp. q << k and q >> k. As described under
Conventions, the shift count may be any primitive integer type,
signed or unsigned, with a negative count reversing the direction. One habit from the integer
types can be dropped here: Natural and Integer floor when shifted right, but a Rational
has nowhere to lose bits to, so both directions are exact, just as GMP’s mpq_div_2exp is.
mpq_div. x / y, with x /= y and x /= &y for the in-place forms. GMP declares
division by zero undefined; Malachite panics, so the failure is at the division site, not
wherever the undefined behavior eventually surfaces.
mpq_neg, mpq_abs. -x and x.abs(), with
NegAssign
and
AbsAssign
mutating in place. Both are sign adjustments that touch no limbs.
mpq_inv. x.reciprocal(), or x.reciprocal_assign() in place. In both libraries this is
an exchange of numerator and denominator, constant time, with canonical form preserved for free.
The zero case differs in the usual way: GMP’s manual warns that inverting a zero “will divide by
zero”, while Rational::reciprocal panics with the message “Cannot take reciprocal of zero”.
Comparison Functions
| GMP | Malachite | |
|---|---|---|
| ✓ | int mpq_cmp (const mpq_t op1, const mpq_t op2) |
Ord |
| ✓ | int mpq_cmp_z (const mpq_t op1, const mpz_t op2) |
PartialOrd |
| ✓ | int mpq_cmp_ui (const mpq_t op1, unsigned long int num2, unsigned long int den2) |
PartialOrd |
| ✓ | int mpq_cmp_si (const mpq_t op1, long int num2, unsigned long int den2) |
PartialOrd |
| ✓ | int mpq_sgn (const mpq_t op) |
Sign |
| ✓ | int mpq_equal (const mpq_t op1, const mpq_t op2) |
PartialEq |
As on the integer page, comparison is where Malachite provides every combination of types: a
Rational
compares against another Rational, an
Integer, a
Natural, any
primitive integer, or any primitive float, in either order. Three discussions there carry over
unchanged: what the comparison returns, an
Ordering rather than a
sign-carrying int, for Sign
too; why single-type rows say Ord while mixed-type rows say PartialOrd; and the warning
about macros that evaluate their arguments more than once, which covers mpq_cmp_ui,
mpq_cmp_si, and mpq_sgn.
mpq_cmp_ui, mpq_cmp_si. These compare against a fraction, not just an integer. When the
denominator is 1, the mixed comparison is direct: q < 5u32. For a general fraction, build it:
q < Rational::from_unsigneds(3u32, 4u32). GMP notes that num2 and den2 may have common
factors; from_unsigneds removes them first, with a word-sized gcd, where GMP instead
cross-multiplies without reducing.
Comparing against a float. GMP’s rational chapter has no mpq_cmp_d; a C program compares
an mpq_t with a double by converting one to the other’s type first, choosing which end loses
precision. Malachite’s PartialOrd<f64> for Rational is exact, since every finite double is
a rational, and a NaN yields
None rather than an undefined
result.
mpq_equal. GMP provides mpq_equal as a separate entry point because it is “much faster”
than mpq_cmp: two canonical rationals are equal exactly when their numerators and denominators
match componentwise, no cross-multiplication required, and canonical form is what makes that
shortcut sound. In Malachite the shortcut is the only path: == is the derived structural
equality on sign, numerator, and denominator, and the ordering work belongs to
Ord alone. The same invariant makes the derived
Hash agree with ==, so a
Rational works as a hash-map key with no extra care, something an mpq_t needs a
canonicalization discipline to offer.
The absolute-value comparison family described on the integer page,
OrdAbs,
PartialOrdAbs,
and
EqAbs,
extends to Rational as well; GMP’s rational chapter has no cmpabs functions to map onto it.
Applying Integer Functions to Rationals
This is the section where the two representations part ways. An mpq_t is a pair of mpz_ts,
a signed numerator and a positive denominator, and the section’s premise is direct access to
those halves so that any integer function can be applied to them, with the manual cautioning
that “care should be taken” to leave the value canonical afterwards. A
Rational is a
sign and two Naturals, so
the sign is not part of the numerator, and mutable access is mediated to keep canonical form
intact. Reading is as direct as in GMP; writing trades raw references for closures that clean up
after themselves.
| GMP | Malachite | |
|---|---|---|
| ≈ | mpz_ptr mpq_numref (const mpq_t op) |
numerator_ref, mutate_numerator |
| ≈ | mpz_ptr mpq_denref (const mpq_t op) |
denominator_ref, mutate_denominator |
| ≈ | void mpq_get_num (mpz_t numerator, const mpq_t rational) |
to_numerator, into_numerator |
| ✓ | void mpq_get_den (mpz_t denominator, const mpq_t rational) |
to_denominator, into_denominator |
| ≈ | void mpq_set_num (mpq_t rational, const mpz_t numerator) |
mutate_numerator, from_sign_and_naturals |
| ✓ | void mpq_set_den (mpq_t rational, const mpz_t denominator) |
mutate_denominator |
Where the sign went. Every ≈ in this section is the same difference: GMP’s numerator
carries the sign, and Malachite’s accessors deal in magnitudes, with the sign asked for
separately, by comparing with zero or through
Sign.
When you need GMP’s signed numerator as a value,
Integer::from_sign_and_abs(q >= 0, q.to_numerator()) builds it, and
from_sign_and_abs_ref
borrows the magnitude rather than consuming it.
mpq_numref, mpq_denref, reading.
numerator_ref
and
denominator_ref
return &Natural at no cost, and any
Natural method
applies: q.denominator_ref().is_power_of_2(), q.numerator_ref().significant_bits(), and so
on, which is the section’s premise, unchanged.
mpq_numref, mpq_denref, writing; mpq_set_num, mpq_set_den. Writing through GMP’s
references, or with mpq_set_num and mpq_set_den, leaves the caller responsible for
canonicalizing. Malachite mediates the same access through closures:
q.mutate_numerator(|n| ...) and q.mutate_denominator(|d| ...) hand the closure a
&mut Natural, and when it returns, the value is reduced to lowest terms, a numerator that
became zero resets the sign to nonnegative, and a denominator that became zero panics. The
manual’s “care should be taken” paragraph becomes machinery. For coordinated changes to both
parts there is
mutate_numerator_and_denominator,
which reduces once at the end. A replacement that changes the sign is the one thing a closure
over a magnitude cannot express; for that, rebuild with
Rational::from_sign_and_naturals(sign, num, den), using
into_numerator_and_denominator
to recover the current parts without copying them.
mpq_get_num, mpq_get_den. GMP copies the half out; to_numerator and to_denominator
do the same. Rust adds a third access mode alongside borrowing and copying: into_numerator and
into_denominator consume the
Rational and
move the half out without copying any limbs. mpq_get_den maps exactly, since denominators are
positive in both libraries; mpq_get_num differs by the sign, as above.
Input and Output Functions
| GMP | Malachite | |
|---|---|---|
| ✗ | size_t mpq_out_str (FILE *stream, int base, const mpq_t op) |
Display |
| ✗ | size_t mpq_inp_str (mpq_t rop, FILE *stream, int base) |
FromStr |
As on the integer page, there is no FILE * half to
port: writing is write!(stream, "{q}") for any
Write, reading means pulling a
token out of your reader and handing it to
FromStr, and nothing in
Malachite needs to know about streams. What keeps the rows at ✗ is the base: these are
mpq_get_str and mpq_set_str with a stream attached, and they inherit the gap described under
Conversion Functions, along with the absence of mpq_inp_str’s base-0
prefix detection.
GMP’s rational chapter has no raw-format I/O, but the serde support described on the integer page
covers Rational too: with the enable_serde feature it implements
Serialize and
Deserialize, encoding a value as a
small struct holding the sign and the two halves in the hexadecimal-string form used for
Natural.
Deserialization validates canonical form, rejecting a zero denominator, a negative zero, and a
fraction not in lowest terms. So the invariant this page opened with holds across serialization
as well: code downstream of a Deserialize never sees a non-canonical
Rational.