nettrash-a 0.1.29

Algorithms in pure Rust with no dependencies: 23 sorting algorithms and a default sort; arbitrary-precision integers with Karatsuba, Toom-Cook and NTT multiplication, Newton division, GCD, roots, modular powers, primes and modular square roots; elliptic curves over prime fields with secp256k1, P-256, P-384, P-521, Curve25519 and Ed25519; complex numbers; matrices of floats, complex numbers or integers with LU, QR, Cholesky, SVD, Schur and eigendecompositions, exp, sqrt and log, and exact integer determinants and inverses. Not constant-time.
Documentation

A

rust workflow crates.io docs.rs license

Algorithms in pure Rust, with no dependencies:

  • Sorting (a::sort): 23 sorting algorithms by comparison and by key, and a default sort.
  • Integers of any size (a::math::numbers): Number, with its arithmetic up to transforms for the longest products, roots, powers modulo a number, greatest common divisors, primes and text in any radix.
  • Elliptic curves (a::math::ec): Weierstrass, Montgomery and Edwards curves over prime fields, secp256k1, P-256, P-384, P-521, Curve25519 and Ed25519 among them.
  • Complex numbers (a::math::complex) and matrices (a::math::matrix): decompositions, eigenvalues, singular values and functions of matrices of floats and complex numbers, and exact algorithms on matrices of integers.

Every algorithm has a page with its method, examples and measurements against the reference implementations (GMP, OpenSSL, faer, nalgebra and others); the tables below link to them. The API reference is on docs.rs. The crate needs Rust 1.88 (edition 2024). Its fastest paths are NEON and assembly kernels on aarch64, and assembly for the products of integers on x86_64; everything else is portable Rust.

Usage

Add the crate with cargo add nettrash-a, or to Cargo.toml:

[dependencies]
nettrash-a = "0.1"

Its library is named a:

use a::math::matrix::Matrix;
use a::math::numbers::Number;
use a::sort::sort;

fn main() {
    let mut v = vec![5, 1, 4, 2, 8, 0, 3, 9, 7, 6];
    sort(&mut v);
    assert_eq!(v, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

    let x: Number = "123456789012345678901234567890".parse().unwrap();
    assert_eq!((&x * &x).to_string(), "15241578753238836750495351562536198787501905199875019052100");

    let a: Matrix<f64> = Matrix::from_rows(&[[4.0, 2.0], [2.0, 3.0]]);
    let b = Matrix::from_rows(&[[2.0], [1.0]]);
    let solution = a.solve(&b).unwrap();
    assert!((solution[(0, 0)] - 0.5).abs() < 1e-15 && solution[(1, 0)].abs() < 1e-15);
}

Sorting

Every sort takes a mutable slice and leaves it sorted; the extra memory is what a sort uses besides the slice. sort is the default: use it when there is no reason to prefer a particular algorithm.

Comparison sorts

These sort any T: Ord.

Sort Functions Stable Best Average Worst Extra memory
Default sort yes O(n) O(n log n) O(n log n) O(n)
Bubble bubble yes O(n) O(n²) O(n²) O(1)
Selection selection no O(n) O(n²) O(n²) O(1)
Insertion insertion yes O(n) O(n²) O(n²) O(1)
Cocktail Shaker cocktail_shaker yes O(n) O(n²) O(n²) O(1)
Gnome gnome yes O(n) O(n²) O(n²) O(1)
Comb comb no O(n log n) O(n log n)¹ O(n²)¹ O(1)
Merge merge yes O(n) O(n log n) O(n log n) O(n)
Quick quick no O(n) O(n log n) O(n log n) O(log n)
Heap heap no O(n) O(n log n) O(n log n) O(1)
Tim tim yes O(n) O(n log n) O(n log n) O(n)
Intro intro no O(n) O(n log n) O(n log n) O(log n)
Shell shell no O(n) unknown² O(n^(4/3))² O(1)
Drift drift yes O(n) O(n log n) O(n log n) O(n)
Tree tree yes O(n) O(n log n) O(n log n) O(n)
Cycle cycle no O(n) O(n²) O(n²) O(1)
Bitonic bitonic, bitonic_parallel no O(n log² n) O(n log² n) O(n log² n) O(log n)
Odd-Even odd_even, odd_even_parallel yes O(n) O(n²) O(n²) O(1)
Stooge stooge no O(n^2.71) O(n^2.71) O(n^2.71) O(log n)

¹ About n·log₁.₃ n comparisons in practice; no O(n log n) bound has been proved, and no known input reaches O(n²).

² No exact bound is known; measured, 1.6–2.8·n·log₂ n comparisons up to a million elements. The worst case is O(n^(4/3)) with Sedgewick's gaps (elements larger than 8 bytes); no bound is known for Ciura's (the others).

Key sorts

These sort by integer or float keys without comparing elements. k is the number of possible keys between the smallest and the largest; integer keys are the primitive integers, char and bool.

Sort Functions Keys Stable Time Extra memory
Counting counting, counting_by_key integer counting_by_key O(n + k) O(k), O(n + k) by key
Radix radix, radix_by_key integer radix_by_key O(n·b/r) for keys that differ in b bits, digits of r ≈ log₂ n bits O(n)
Bucket bucket, bucket_by_key integer, f32, f64 bucket_by_key O(n) for keys spread evenly, O(n·w) for w-bit keys at worst O(n)
Pigeonhole pigeonhole, pigeonhole_by_key integer no O(n + k) O(k), O(n + k) by key
Sleep sleep integer — (max − min) × unit of sleeping a thread per element

Sleep sort is a joke: a thread per element sleeps in proportion to its value, the result is not always sorted, and sleep returns whether it is.

Against the standard library

Measured on an Apple M4 Max with new input for every sort; each page has the other inputs, sizes and element types:

Sort Against Speed
sort slice::sort 1.3–3 times faster on 8–64 random integers, 1.03–1.06 times slower from 100 to 100,000 and as fast beyond; 1.7 times faster on a million random strings
merge slice::sort 1.6–2.4 times faster on up to 64 random integers, 1.08–1.23 times slower from 1000; 3–6 times slower with few distinct keys
tim slice::sort 1.2–1.45 times slower on random integers from 1000, 1.5–3.7 times faster on nearly sorted input
quick slice::sort_unstable 1.1–1.2 times slower on random integers from 100
intro slice::sort_unstable 1.45–1.75 times slower on random integers
radix slice::sort_unstable 1.8–5 times faster on random u32s from 1000, up to 2.3 times on random u64s
counting slice::sort_unstable 3.2–10 times faster on random u32s and u64s with about as many distinct keys as elements, from 100
bucket slice::sort_unstable 1.3–3 times faster on random integers and 1.7–3.7 times on uniform floats, from 100
pigeonhole slice::sort_unstable 1.15–2.2 times faster on random u32 keys in a range of about n, from 1000

Math

Integers of any size

a::math::numbers::Number is an integer of any size, its sign and the bytes of its absolute value in either byte order. Operators take owned or borrowed operands and build the result in an operand's vector when it has room.

Page Contents
Numbers Number: how it is stored, built and read
Traits and conversions Comparison and hashing by value; conversions from and to the primitive integers, to f64 and f32, and from strings; Sum and Product; arithmetic with primitive integers
Addition + and +=
Subtraction - and -=
Multiplication * and *=: schoolbook, Karatsuba, Toom-3, Toom-4 and number-theoretic transforms, with assembly kernels on aarch64 and x86_64
Division /, %, div_rem, Euclid's division, a prepared Divisor and exact division: Knuth's method, divide and conquer, and Newton's method for the longest
Shifts << and >> by any number of bits, rounding as i32 does
Roots isqrt, isqrt_rem and nth_root: Zimmermann's Karatsuba square root and Newton's method
Factorials factorial and binomial, from prime factorisations by balanced product trees
Bitwise operations &, |, ^ and ! in two's complement; bits, count_ones, trailing_zeros, bit and set_bit
Text Display, hexadecimal, octal and binary formatting; to_str_radix and from_str_radix in any radix from 2 to 36
Greatest common divisor gcd, lcm, gcd_ext and mod_inverse: Lehmer's method and the subquadratic half gcd
Powers pow, and pow_mod by Montgomery's multiplication
Primes is_probable_prime by Baillie–PSW, exact below 2⁶⁴, and next_prime
Squares modulo a prime jacobi, and sqrt_mod by Tonelli and Shanks's method

Measured on an Apple M4 Max in cycles per call, over random operands of the length given, little-endian, against GMP 6.3 and num-bigint 0.4; each page has the conditions, more sizes and the other operations. GMP is faster on some short operands; "–" is not measured:

Operation Size a GMP num-bigint
a + b 64 B 25 37 –
4 KiB 469 585 –
a * b 64 B 109 107 217
1 KiB 9,545 11,261 29,665
64 KiB 1.12 M 2.85 M 17.1 M
1 MiB 24.5 M 86.3 M 1,058 M
a / b 64 B / 32 B 197 174 738
1 KiB / 512 B 4,401 5,148 22,811
64 KiB / 32 KiB 1.30 M 2.82 M 15.0 M
isqrt 64 B 426 335 2,798
1 KiB 4,770 4,615 101,731
64 KiB 1.46 M 2.31 M 136 M
gcd 64 B 5,138 6,853 26,954
1 KiB 117,118 163,209 1.15 M
64 KiB 55.5 M 64.4 M 4,013 M
pow_mod, the exponent as long as the modulus 64 B 119,000 128,779 271,225
256 B 4.30 M 5.13 M 9.09 M
1 KiB 233 M 239 M 683 M
to_str_radix(10) 128 B 983 2,453 4,417
8 KiB 504,112 607,962 2.33 M
64 KiB 7.29 M 12.6 M 50.3 M
is_probable_prime of a prime 256 bits 68,457 159,951 –
2048 bits 14.9 M 20.9 M –

Elliptic curves

Page Contents
Elliptic curves a::math::ec: short Weierstrass, Montgomery and twisted Edwards curves over prime fields of any length; +, -, *, compression, multi_mul, prepared points and maps between the forms; secp256k1, P-256, P-384, P-521, Curve25519 and Ed25519 ready made

Measured on an Apple M4 Max in cycles, against OpenSSL 3.6 and curve25519-dalek 4.1; k·P multiplies a point, k·G the generator, by a table where the library has one. OpenSSL's and dalek's products of a point are constant-time, which costs them time this crate doesn't spend:

Curve Against k·P theirs k·G theirs
secp256k1 OpenSSL 40,100 555,000 7,250 559,000
P-256 OpenSSL 82,600 90,900 10,600 14,300
P-384 OpenSSL 311,000 548,000 40,200 267,000
P-521 OpenSSL 358,000 496,000 52,500 269,000
Ed25519 dalek 45,300 72,000 5,350 22,100
Curve25519, X25519 dalek 55,400 78,500

Complex numbers

Page Contents
Complex numbers a::math::complex: Complex<f32> and Complex<f64>, their arithmetic, abs, arg, exp, ln, sqrt and powers, with C99's branch cuts; division and square roots that don't overflow before their results do

Measured on an Apple M4 Max for Complex<f64>, in nanoseconds per operation over 1,024 independent ones, against num-complex 0.4.6, whose division by |b|² as it is overflows from |b| of about 10¹⁵⁴:

Operation Complex num-complex
a * b 0.43 0.44
a / b 1.99 0.88
sqrt 4.41 9.12
exp 5.15 5.46
ln 7.54 7.40
powc 15.75 15.48

Matrices

a::math::matrix::Matrix<T> is a dense matrix of any size, column after column, of f32, f64, Complex<f32>, Complex<f64> or Number. The algorithms are LAPACK's, arranged so that most of their work is products of blocks, and the products run on NEON kernels; real matrices stay in real arithmetic. Costs are for n × n matrices.

Operations Functions Method Cost
Arithmetic +, -, *, by an element, hadamard, transpose, conjugate_transpose, row and column operations, trace, pow, powi the product by Goto and van de Geijn's blocking 2n³ for a product
LU lu, det, solve, inverse partial pivoting, recursive; the inverse as LAPACK's getri 2n³/3, the inverse 2n³
Cholesky cholesky recursive, blocked n³/3
QR qr, q, q_full, least squares Householder reflections in blocks, the panels halved recursively 4n³/3
Singular values svd, singular_values, rank, null_space, norm, cond a blocked reduction to bidiagonal form, then divide and conquer, or dqds for the values alone 9.3n³, the values 8n³/3
Hermitian eigenvalues hermitian_eigen, hermitian_eigenvalues a blocked reduction to tridiagonal form, then divide and conquer 4.7n³, the values 4n³/3
Eigenvalues schur, eigen, eigenvalues, characteristic_polynomial a blocked reduction to Hessenberg form, then the multishift QR algorithm with aggressive early deflation about 25n³, the values 10n³
Functions exp, sqrt, log scaling and squaring with Padé approximants; the Schur method; inverse scaling and squaring exp a few products, sqrt and log a Schur decomposition
Exactly, on Number det, rank, characteristic_polynomial, adjugate, inverse, null_space Bareiss's elimination and Berkowitz's method, without fractions n³ to 3n⁴/2 products of numbers

In f64 on one core of an Apple M4 Max, each of the twelve operations measured was at least as fast as faer's and nalgebra's at every size from 8 × 8 to 1024 × 1024; at 1024 × 1024, in milliseconds:

n = 1024 a faer nalgebra
a * b 34.9 38.4 37.1
lu() 13.8 17.2 59.9
inverse() 40.1 50.2 221
cholesky() 7.4 8.5 27.8
qr() 29.0 32.5 90.7
svd() 244 331 5,886
hermitian_eigen() 114 146 396
eigen() 498 760 –
exp() 512 – 778

Apple's Accelerate, which runs its products on the M4's matrix unit, is faster than all three at this size; it has no exp.

Not constant-time

Nothing here is written to take the same time whatever its operands: pow_mod and the elliptic curves in particular branch on the values of their exponents and scalars. Use them on public data, for verification and research, not on secret keys or scalars.

Changelog

What each version added, changed and fixed is in CHANGELOG.md.

License

MIT.