#integer #int #unbounded #unsigned #large

large_int

An ease-of-use unbounded signed integer

4 releases

1.0.0 Jun 14, 2020
0.2.2 Jun 18, 2020
0.2.1 Jun 15, 2020
0.2.0 Jun 15, 2020
0.1.0 Jun 14, 2020

#1135 in Data structures

MIT/Apache

64KB
1K SLoC

LargeInt

An integer datatype that (practically) has no limit on size. This implementation aims to provide a means of performing calculations easily and intuitively with numbers larger than the largest primitive integer type provided in Rust (128 bits). It also aims to be able to integrate easily with existing primitive types, such that they can be added and subtracted easily from LargeInts. LargeInt is always signed, but because it can grow indefinitely there is no downfall to this.

When to Use LargeInt

LargeInt should ONLY be used when values are expected to go beyond u128::MAX. I find the likelihood of this datatype ever really being useful slim (since 128 bits already provides VERY large numbers), however it's a nice-to-have for anyone who may find themselves in need of it. That said, I mostly built it for fun and out of intrigue. Using this type in circumstances outside of this is not recommended because it will be slower than simply using a primitive type.

How to Use LargeInt

See the example folder for an example on how to use the LargeInt type. Long story short, after initialisation operations can be performed on the numbers using the same operators that can be used with primitive types (i.e. +, -, /, *, |, ^, &). These operations consume the LargeInt (the Copy trait cannot be derived for types containing Vecs) so if you don't want to consume the variable it will require being cloned before performing the operation.

LargeInt can be initialised from strings (LargeInt::from_str("123")) or primitive signed or unsigned integers (LargeInt::from(123)).

Example

use large_int::large_int::LargeInt;

fn main() {
    let mut li = LargeInt::from_str("340282366920938463463374607431768211455").unwrap();
    li += 20451;
    assert_eq!(li, LargeInt::from_str("340282366920938463463374607431768231906").unwrap());

    let smaller = li / 100000000000000000000000000000000_u128;
    assert_eq!(smaller, 3402823_u128.into());

    let multed = smaller * 3;
    assert_eq!(multed, 10208469.into());

    let subbed = multed - 20000000;
    assert_eq!(subbed, -10208459.into());
}

Operations Limitations

Add and Sub are the simplest and least expensive operations and are completed in O(n) time for a number represented with n u128 values (assuming the number being added to it is sized <=_n_).

Mul and Div are more expensive operations. Multiplication is achieved in O(nm) time for n being the largest representation of either value and m being the smallest number of 1's in the binary representation of either number. Division is similar but cannot optimise to the smallest number of 1's due to the operation order being important.

Printing these numbers is by far the most expensive operation as it requires multiple additions and multiplications in order to calculate the decimal representation (as many as the number of digits in the representation). You should only print LargeInts when entirely necessary. In order to ease the requirement of this method they are formattable when printing. This provides a means by which to print the values in scientific notation (e.g. 123 == 1.23*10^2). This format does not round digits.

Shifting operators (i.e. << and >>) do not change the number of u128 values used to represent the number. This means shifting left will not infinitely shift digits left, but will overflow and be lost.

In order to abstract the representation of the numbers the "op_no_shrink" methods have been kept private.

The Div (/) and Rem (%) operators perform the same operation but return different values. If both values are desired it's better to use the "div_with_remainder" method that returns both values as a pair (result, remainder), rather than to call both Div and Rem separately, since this will double the time taken.

Change Log

0.2.2

Adds the ability to use primitives as the first/left argument for operations.

Changed implementation such that any type implementing Into<LargeInt> (i.e. From<type> for LargeInt) can be used with LargeInts. In order to support being the left argument of the operation, use the macro reverse_operations.

0.2.1

Ammended the exclusion of TryFrom for unsigned integer types.

Additionally the div_with_remainder method was made generic such that it now supports both primitives and other LargeInts (and any other type implementing Into<LargeInt>).

0.2.0

Added extra generic traits Default, Eq, Hash, Ord, LowerExp. Also added iterator traits Sum and Product.

New functionality implemented TryFrom trait for all primitive int types. Unsigned ints were excluded by mistake.

More new functionality included mirror methods for the primitive integer types, pow and abs.

0.1.0 (First release)

Initial release included most of the standard std::ops operators. The base functionality was complete and included in this release.

1.0.0 (Yanked initial version)

This version was yanked due to issues with the categorisation on crate.io (after yanking I also realised the version should probably not be 1 yet).

No runtime deps