Basic Types
Rusts data types such as u64 and i64 do not capture arbitrarily large integers.
For example, the following code causes an overflow:
let overflow: u64 = u64::MAX + 1;
qFALL-math works with arbitrarily large integers and the user can work with them without needing to consider potential overflows and execution failures.
Instantiate basic types
qFALL-math’s basic types are integers (Z), integers in the quotient ring (Zq) and rationals (Q).
Each can be instantiated from strings and Rust’s basic math types.
use qfall_math::{integer::Z, integer_mod_q::Zq, rational::Q};
use std::str::FromStr;
fn basic_instantiations() {
// instantiations using rust types
let z = Z::from(42);
let zq = Zq::from((24, 42));
let q = Q::from((24, 42));
// instantiations using strings
let z = Z::from_str("42").unwrap();
let zq = Zq::from_str("24 mod 42").unwrap();
let q = Q::from_str("24/42").unwrap();
}
Zq and its Modulus
To identify an element in the quotient ring over the integers, we need a representative (a value in Z) and context information defining the quotient (Modulus).
A modulus object is of type Modulus and can be instantiated from a Z greater or equal to two.
use qfall_math::{integer::Z, integer_mod_q::Modulus};
fn instantiate_modulus() {
let z = Z::from(42);
let modulus = Modulus::from(&z);
let modulus = Modulus::from(42);
}
A Zq value consists of a Z value and a Modulus.
A modulus can be shared among different values:
use qfall_math::{
integer::Z,
integer_mod_q::{Modulus, Zq},
};
fn shared_modulus() {
let modulus = Modulus::from(42);
let zq_1 = Zq::from((17, &modulus));
let zq_2 = Zq::from((24, &modulus));
}
Each Modulus has a reference counter hidden behind them.
Therefore, if a Modulus is cloned, this only allocates another pointer to the underlying object, making it efficient to store a Z and a Modulus together.
Storing them together has two advantages:
- mathematical soundness, i.e., no accidental modulus switching or operations between objects with different moduli can occur
- easier use, as the developer does not have to carry the modulus manually.
Converting types
All reasonable type conversions are possible, e.g. we can define a Q from a Z.
use qfall_math::{integer::Z, rational::Q};
fn q_from_z() {
let z = Z::from(42);
let q = Q::from(&z);
}
Basic arithmetics
All types support basic arithmetics. Among them are multiplication, addition and subtraction.
Opposed to what is typical Rust behavior, we natively support operations between different types, e.g., Z and Q, as there is a mathematical reasonable output of Q that can be expected, and as we are working with arbitrarily large values, no memory leaks or rounding can occur.
use qfall_math::integer::Z;
use qfall_math::rational::Q;
fn basic_arithmetics() {
let z_1 = Z::from(42);
let z_2 = Z::from(24);
let add = &z_1 + &z_2;
let sub = &z_1 - &z_2;
let mul = &z_1 * &z_2;
let add: Q = &z_1 + Q::from((1, 2));
}
The operations between types either hide optimized behavior or implicit type casts.
Similarly, it is also to combine our types directly with Rust’s types, e.g., f64.
Printing your Results
You can print the values of operations using print! or println!, or
convert them into strings with .to_string().
This naturally extends to more complex structs as well.
use qfall_math::integer_mod_q::Zq;
fn print() {
let zq_1 = Zq::from((24, 42));
let zq_2 = Zq::from((17, 42));
let zq = zq_1 - zq_2;
// print it directly
println!("{zq}");
// turn it into a string first
let zq_string = zq.to_string();
println!("{}", zq_string)
}
Error Handling
In our math crate, we have one file error.rs, containing all our error types.
We use MathError as our error enum that holds all sorts of errors occurring in
this crate and StringConversionError for all errors that are related to the conversion of strings.
Errors are normally propagated through functions such that they can easily be handled or ignored with unwrap().
In cases where errors are easily visible, like “division by zero” errors, we do not propagate the error but panic instead.
That is to evade unnecessary uses of unwrap().