Malachite for num Users: Rationals
This page maps the type of the
num-rational crate, Ratio<T>, and
chiefly its bignum instantiation BigRational, onto its Malachite counterpart:
Rational,
from the malachite-q crate. It covers num-rational 0.4.2 as re-exported by the
num umbrella crate. It is a companion to
Malachite for num Users: Integers on the num side, the components of
a BigRational being that page’s BigInts, and to
the GMP and FLINT rationals pages on
the type’s side, since all three map onto the same Rational; the
mapping index lists the whole family. As on the integers page, num-rational has
no manual to follow, so the sections below are organized by theme and cover the type’s full
public surface, inherent methods and trait implementations together.
One note on routes: the drop-in crate malachite-bigint reimplements the num-bigint API only, so for rationals the native route described here is the route.
Conventions
The types
Ratio<T> is generic over its component type, and num-rational names four instantiations:
Rational32 and Rational64 over 32- and 64-bit components, the deprecated Rational over
isize, and BigRational over BigInt, available behind the num-bigint feature, which is
on by default. Malachite has one rational type, with arbitrary-precision components. The
mapping below reads most directly against BigRational, but every Ratio value embeds in a
Rational,
and one behavioral difference follows from the widths: arithmetic on the fixed-size
instantiations can overflow its component type, while Rational arithmetic always produces
the exact value. Code ported from Rational64 sheds those overflow cases rather than
translating them.
Canonical form
The spectrum described on the FLINT rationals page
gets a third act, and num sits where fmpq_t does: the library maintains canonical form,
with one documented door out of it. Ratio::new reduces, normalizes the sign so that the
denominator stays positive, gives zero the denominator 1, and panics on a zero denominator;
arithmetic keeps results reduced; and num states that “the only method of procuring a
non-reduced fraction is through new_raw”, which creates a Ratio “without checking for
denom == 0 or reducing”, with the library’s own bold warning that “there are several
methods that will panic if used on a Ratio with denom == 0”. The reduced method exists
to repair what new_raw may have built. Malachite closes that door too:
Rational’s
canonical form, the same form num maintains, is
unbreakable, component
access being read-only or mediated by reducing closures, so the new_raw/reduced pair and
the hazard they bracket have no counterparts here.
The representation
A Ratio<T> is a numerator and a denominator, each a T, so a BigRational keeps its sign
inside the numerator BigInt; a Rational is a sign and two
Natural
magnitudes. This is the same placement difference discussed on
the GMP rationals page,
and it surfaces wherever components are taken apart or reassembled.
Idiom and features
The conventions of the integers page carry over:
operators on owned and borrowed operands with *Assign forms, values rather than initialized
variables, exact types with no rounding modes. The serde features correspond, with the wire
formats differing as described under serialization below. num-rational has no random-number
support; Malachite’s random feature covers
Rational
as it does the other types. Both libraries work without std.
Categories
Each item 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; the notes say why. |
| ✗ | Malachite does not fully support this yet, but will in a future version. |
Construction and components
| num | Malachite | |
|---|---|---|
| ✓ | Ratio::new (numer: T, denom: T) -> Ratio<T> |
from_integers, from_signeds |
| — | Ratio::new_raw (numer: T, denom: T) -> Ratio<T> |
|
| ≈ | numer (&self) -> &T |
numerator_ref |
| ✓ | denom (&self) -> &T |
denominator_ref |
| ≈ | into_raw (self) -> (T, T) |
into_numerator_and_denominator |
| ✓ | Ratio::from_integer (t: T) -> Ratio<T> |
From |
| ✓ | From<T>, From<(T, T)> |
From, from_integers |
| — | reduced (&self) -> Ratio<T> |
|
| ✓ | Default |
Default |
| ✓ | Zero, ConstZero (zero(), ZERO, is_zero) |
Zero |
| ✓ | One, ConstOne (one(), ONE, is_one) |
One |
The constructors. Ratio::new(n, d) and
Rational::from_integers(n, d)
agree on everything: both reduce, both normalize the sign into the numerator, and both panic
on a zero denominator. The word-component instantiations construct through
from_signeds
and from_unsigneds, so Rational64::new(a, b) ports as Rational::from_signeds(a, b), and
from_sign_and_naturals completes the set for callers holding a sign and two magnitudes.
from_integer, From<T>, and From<(T, T)> are the one-argument spellings,
Rational::from(n) for integers of every size, with the pair form behaving as new does.
The raw door. new_raw and reduced are the two ends of the hazard described under
Conventions: one builds an unchecked value, the other repairs it. Neither has
a counterpart, because unreduced
Rationals
cannot be built; the constructors reduce, and that is the whole story.
The components. denom and
denominator_ref
both lend a positive denominator for free. numer is the ≈ of the
representation difference: num lends a signed &BigInt, while
numerator_ref
lends the magnitude as a
Natural,
with the sign asked for separately. into_raw deconstructs by value, and
into_numerator_and_denominator does the same with the sign read first,
let sign = x >= 0u32; before the components are moved out. Malachite also has a write path
the read-only accessors do not open:
mutate_numerator
and mutate_denominator admit a closure to a component and re-reduce afterward, the mediated
access described on
the GMP rationals page.
The constants. Default “returns zero” on both sides. Zero and One, with their
ConstZero and ConstOne refinements, map to the
Zero
and One associated constants, const from the start; Malachite’s shelf continues with
Two, NegativeOne, and OneHalf, so Rational::ONE_HALF is a constant rather than a
construction.
Comparison
| num | Malachite | |
|---|---|---|
| ✓ | PartialEq, Eq |
PartialEq, Eq |
| ✓ | PartialOrd, Ord |
PartialOrd, Ord |
| ✓ | Hash |
Hash |
| ✓ | Signed::is_positive, is_negative |
PartialOrd |
The standard traits. Exact types, total numeric order, on both sides. The
implementations reach the same answers from opposite directions, in a way worth understanding
when porting new_raw-using code: because an unreduced Ratio can exist, num implements
equality through comparison and hashes by walking the continued-fraction expansion, its
source noting that a derived hash “needs to agree with Eq even for non-reduced ratios”;
Malachite’s equality and hash are structural, and agree with value semantics because every
Rational is
canonical. On values built by ordinary constructors the two libraries are observably
identical. The Signed predicates are the mixed comparisons x > 0u32 and x < 0u32, false
for zero on both sides; the rest of Signed appears with the arithmetic.
Mixed comparisons and the magnitude order. num-rational compares Ratios against
Ratios; Malachite’s mixed implementations extend to
Integer,
Natural,
every primitive integer, and every primitive float, the float comparisons exact and
allocation-free, in both argument orders. The magnitude order comes along as it did
on the integers page:
OrdAbs,
PartialOrdAbs,
and
EqAbs
compare |x| with |y| directly. One further order exists here:
cmp_complexity,
the simplicity well-order behind Malachite’s simplest-in-interval machinery, described on
the FLINT rationals page.
Arithmetic
| num | Malachite | |
|---|---|---|
| ✓ | Add, Sub, Mul, Div, *Assign |
Add, Sub, Mul, Div, *Assign |
| ≈ | Rem, RemAssign |
Sub, Div |
| ✓ | Neg |
Neg, NegAssign |
| ✓ | recip (&self); Inv |
Reciprocal, ReciprocalAssign |
| ✓ | pow (&self, expon: i32); Pow (primitive and bignum exponents) |
Pow, PowAssign |
| — | CheckedAdd, CheckedSub, CheckedMul |
|
| ≈ | CheckedDiv |
Div |
| ✓ | Sum, Product |
Sum, Product |
| ✓ | Signed::abs |
Abs, AbsAssign |
| ≈ | Signed::abs_sub |
Sub |
| ✓ | Signed::signum |
Sign |
The operators. The four operations and their *Assign forms exist in all owned and
borrowed combinations on both sides, with division by zero a panic on both. Neither library
implements operators between a rational and a scalar, so the asymmetry noted on
the integers page does not recur here: mixed
operands lift on both sides, r + Ratio::from_integer(c) and r + Rational::from(c).
Rem. num-rational carries % over from the primitives: a % b is what remains after
removing the truncated quotient, \(a - \operatorname{trunc}(a/b) \cdot b\). Malachite spells
the definition out, a division, a truncation as in
the next section, a multiplication, and a subtraction; rational
division being exact, nothing else hides in the operator.
Reciprocals and powers. recip and Inv are
Reciprocal
and its assign form, and the reciprocal of zero panics on both sides. pow takes an i32
inherently and any primitive or bignum exponent through Pow; Malachite’s
Pow
takes i64 and u64, the bignum-exponent forms being
wider in type only as on the integers page. A
negative exponent inverts first, so zero under a negative exponent panics on both sides.
The checked family. The same sorting as on
the integers page: on BigRational,
checked_add, checked_sub, and checked_mul cannot fail and exist for generic-code
uniformity, the traits page’s subject, while
checked_div guards the zero divisor, spelled
(y != 0u32).then(|| x / y). On the fixed-size instantiations the first three are real
overflow guards, which is part of the
width difference described under Conventions rather than a mappable
operation.
The Signed arithmetic. abs is
Abs
with AbsAssign; abs_sub is the positive difference again,
if a > b { a - b } else { Rational::ZERO }; and signum reads
Sign:
Greater, Equal, and Less become 1, 0, and -1 as Rationals. Sum and Product
fold iterators of values or references on both sides.
Beyond the dictionary. Malachite’s
Shl
and Shr scale a
Rational by
powers of two exactly, in either direction and by signed or unsigned amounts, and
Square
with SquareAssign is the dedicated squaring; num-rational spells these r * &two_pow and
&r * &r.
Rounding and conversion
| num | Malachite | |
|---|---|---|
| ✓ | floor (&self) -> Ratio<T> |
Floor |
| ✓ | ceil (&self) -> Ratio<T> |
Ceiling |
| ≈ | round (&self) -> Ratio<T> |
RoundingFrom |
| ✓ | trunc (&self) -> Ratio<T> |
RoundingFrom |
| ✓ | fract (&self) -> Ratio<T> |
Sub |
| ✓ | to_integer (&self) -> T |
RoundingFrom |
| ✓ | is_integer (&self) -> bool |
is_integer |
| ✓ | BigRational::from_float (f: T) -> Option<BigRational> |
TryFrom |
| ≈ | approximate_float, approximate_float_unsigned |
Approximate |
| ≈ | ToPrimitive (to_i64, to_u64, to_f64, …) |
RoundingFrom, TryFrom |
| ✓ | FromPrimitive for BigRational (from_i64, from_f64, …) |
From, TryFrom |
Rounding to integers. num’s quartet returns integer-valued Ratios; Malachite’s rounding
returns
Integers,
with Rational::from(i) rewrapping when the rational shape is wanted. floor and ceil are
the Floor
and Ceiling traits; trunc and to_integer, which differ only in packaging, are
Integer::rounding_from(&q, Down); and fract is the subtraction its own documentation
defines it by, “satisfies self == self.trunc() + self.fract()”. The ≈ is round: num
“rounds half-way cases away from zero”, while Integer::rounding_from(&q, Nearest) follows
the bankers’ rule, ties to even, and reports the rounding direction as an
Ordering. Ties-away is a
short spelling when compatibility matters:
if q >= 0 { (q + Rational::ONE_HALF).floor() } else { (q - Rational::ONE_HALF).ceiling() }.
is_integer maps to
is_integer
directly.
Floats, exactly. BigRational::from_float converts a float’s exact binary value,
answering None for NaN and infinities, and Rational::try_from(f) is
the same conversion with an Err in those
places; FromPrimitive::from_f64 on BigRational delegates to it, so the exact semantics
carry over. On the fixed-size instantiations from_f64 instead approximates, which belongs
to the width story under Conventions.
Floats, approximately. approximate_float walks a continued fraction toward the float,
bounded by the component type and steered by default tolerances; it exists on the fixed-size
instantiations, Bounded excluding BigInt. Malachite’s relatives parameterize the goal
explicitly:
approximate
finds the best approximation with the denominator bounded by a chosen limit, and
from_float_simplest
finds the simplest
Rational
that rounds to the given float. The surrounding machinery, continued fractions, convergents,
and simplest_rational_in_interval, is mapped in detail on
the FLINT rationals page.
ToPrimitive. The integer targets truncate and then fit, which is
Integer::rounding_from(&q, Down) followed by the integer conversions of
the integers page, with the usual Option-against-Err
shape. For to_f64, the implementations differ by instantiation: with fixed-size components
num divides the two components as f64s, rounding each before the quotient, while
BigRational converts through a scaled division built for the purpose. Malachite’s
f64::rounding_from(&q, rm) rounds the exact value once, in the chosen mode, and reports the
direction.
Strings and serde
| num | Malachite | |
|---|---|---|
| ✓ | Display |
Display |
| ≈ | Debug |
Debug |
| ✗ | Binary, Octal, LowerHex, UpperHex |
|
| ≈ | LowerExp, UpperExp |
ToSci |
| ✓ | FromStr (error: ParseRatioError) |
FromStr |
| ✗ | Num::from_str_radix (s: &str, radix: u32) -> Result |
FromStr |
| ≈ | Serialize, Deserialize |
Serialize, Deserialize |
Display and Debug. The display convention converges exactly, and it is the same
convergence the FLINT page found in
fmpq_print: both libraries print numer/denom and drop the /1, so an integral value
prints as a bare integer. Debug differs: num derives it, printing the struct form with
field names, while Malachite’s Debug prints what Display prints.
Radix strings. num formats a Ratio in binary, octal, and hexadecimal componentwise,
prefixes and all, and parses any radix from 2 to 36 through Num::from_str_radix. Both
directions belong to the base-\(n\) rational string gap
already promised on the GMP rationals page:
Rational
parses and prints base 10 today, through FromStr and Display, and until the general-base
functions land, radix output composes from the integer formatting of
numerator_ref() and denominator_ref().
Scientific notation. num’s LowerExp and UpperExp render each component in exponential
notation, numer’s over denom’s. Malachite’s
ToSci
renders the value: correctly rounded scientific digits under
ToSciOptions
control of precision and rounding mode, with from_sci_string parsing the notation back in.
The two serve presentation differently enough that the row is ≈ rather than a rename.
FromStr. The grammars agree in detail: num “parses numer/denom or just numer”, and
Malachite accepts the same two shapes, where “the numerator and denominator do not need to be
in lowest terms, but the denominator must be nonzero”. Both reduce unreduced text, and both
return an error rather than panicking on a zero denominator, num’s a typed
ParseRatioError, Malachite’s a unit error.
serde. The wire formats differ as they did
on the integers page: num serializes
the pair (numer, denom), each component in num-bigint’s digit-sequence format, while
Malachite writes a three-field struct, the sign and two hexadecimal-string magnitudes. Both
reject a zero denominator when deserializing. One difference runs deeper than format: num
deserializes through new_raw, so unreduced wire data enters as an unreduced Ratio, which
its value-based equality and hash
absorb by design; Malachite’s deserialization checks canonical form outright,
rejecting what does not satisfy it, the validation described on
the GMP rationals page.
As before, data migrates between the libraries through explicit components rather than
directly.
That completes the type-level mapping; the generic traits a Ratio<T> answers to are
the traits page’s subject.