Skip to content

Commit

Permalink
Implement missing multiply with overflow checking (#5)
Browse files Browse the repository at this point in the history
* Adding doc links

* Adding doc badge

* Implement overflowing_mul

* Add multiply test coverage

* Update version, readme
  • Loading branch information
kaidokert authored Apr 10, 2021
1 parent 46eeb8a commit 5fd7d18
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 68 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[package]
name = "fixed-bigint"
version = "0.1.1"
version = "0.1.2"
authors = ["kaidokert <[email protected]>"]
documentation = "https://docs.rs/fixed-bigint"
edition = "2018"
description = """
Fixed-size big integer implementation for Rust
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Fixed BigInt

[![crate](https://img.shields.io/crates/v/fixed-bigint.svg)](https://crates.io/crates/fixed-bigint)
[![documentation](https://docs.rs/fixed-bigint/badge.svg)](https://docs.rs/fixed-bigint/)
[![minimum rustc 1.51](https://img.shields.io/badge/rustc-1.51+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![build status](https://github.com/kaidokert/fixed-bigint-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/kaidokert/fixed-bigint-rs/actions)

Expand All @@ -16,7 +17,8 @@ The crate is written for `no_std` and `no_alloc` environments with option for pa
The arithmetic operands ( +, -, .add() ) panic on overflow, just like native integer types. Panic-free alternatives like `overlowing_add` and `wrapping_add` are supported.

_TODO list_:
* Implement missing checked_mul-div, wrapping_mul/div, overflowing_mul/div.
* Implement WrappingShl/Shr, CheckedShl/Shr
* Implement AddAssign, MulAssign and other xyzAssign operands, memory and speed improvement
* Implement experimental `unchecked_math` operands, unchecked_mul, unchecked_div etc.
* Probably needs its own error structs instead of reusing core::fmt::Error and core::num::ParseIntError
* Decimal string to/from conversion, currently only binary and hex strings are supported.
Expand Down
124 changes: 79 additions & 45 deletions src/fixeduint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use num_traits::{
ops::overflowing::OverflowingAdd, ops::overflowing::OverflowingSub, Bounded, One, PrimInt,
ToPrimitive, Zero,
};
use num_traits::ops::overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub};
use num_traits::{Bounded, One, PrimInt, ToPrimitive, Zero};

use core::convert::TryFrom;
use core::fmt::Write;
Expand Down Expand Up @@ -183,16 +181,6 @@ impl<T: MachineWord, const N: usize> FixedUInt<T, N> {
Ok(())
}

fn from_doubleword(other: T::DoubleWord) -> Self {
let mut ret = Self::zero();
ret.array[0] = T::from_double(other);
if N > 1 {
let tmp2 = other >> Self::WORD_BITS;
ret.array[1] = T::from_double(tmp2);
}
ret
}

// Here to avoid duplicating this in two traits
fn saturating_add_impl(self, other: &Self) -> Self {
let res = self.overflowing_add(&other);
Expand Down Expand Up @@ -409,26 +397,83 @@ impl<T: MachineWord, const N: usize> num_traits::Saturating for FixedUInt<T, N>

// #region Multiply/Divide

impl<T: MachineWord, const N: usize> core::ops::Mul for FixedUInt<T, N> {
type Output = Self;
fn mul(self, other: Self) -> <Self as core::ops::Mul<Self>>::Output {
impl<T: MachineWord, const N: usize> num_traits::ops::overflowing::OverflowingMul
for FixedUInt<T, N>
{
fn overflowing_mul(&self, other: &Self) -> (Self, bool) {
let mut ret = Self::zero();

let mut overflowed = false;
// Calculate N+1 rounds, to check for overflow
let max_rounds = N + 1;
let t_max = T::max_value().to_double();
for i in 0..N {
let mut row = Self::zero();
let mut carry = T::DoubleWord::zero();
for j in 0..N {
if i + j < N {
let intermediate: T::DoubleWord =
self.array[j].to_double() * other.array[i].to_double();
let mut f = Self::from_doubleword(intermediate);
let shiftq: usize = (Self::WORD_BITS * (i + j)) as usize;
f = f << shiftq;
row = f + row;
let round = i + j;
if round < max_rounds {
let mul_res = self.array[i].to_double() * other.array[j].to_double();
let mut accumulator = T::DoubleWord::zero();
if round < N {
accumulator = ret.array[round].to_double();
}
accumulator = accumulator + mul_res + carry;

if accumulator > t_max {
carry = accumulator >> Self::WORD_BITS;
accumulator = accumulator & t_max;
} else {
carry = T::DoubleWord::zero();
}
if round < N {
ret.array[round] = T::from_double(accumulator);
} else {
overflowed = overflowed || !accumulator.is_zero();
}
}
}
ret = ret + row;
if !carry.is_zero() {
overflowed = true;
}
}
(ret, overflowed)
}
}

impl<T: MachineWord, const N: usize> core::ops::Mul for FixedUInt<T, N> {
type Output = Self;
fn mul(self, other: Self) -> <Self as core::ops::Mul<Self>>::Output {
let res = self.overflowing_mul(&other);
res.0
}
}

impl<T: MachineWord, const N: usize> num_traits::WrappingMul for FixedUInt<T, N> {
fn wrapping_mul(&self, other: &Self) -> Self {
self.overflowing_mul(&other).0
}
}

impl<T: MachineWord, const N: usize> num_traits::CheckedMul for FixedUInt<T, N> {
fn checked_mul(&self, other: &Self) -> Option<Self> {
let res = self.overflowing_mul(&other);
if res.1 {
None
} else {
Some(res.0)
}
}
}

impl<T: MachineWord, const N: usize> num_traits::ops::saturating::SaturatingMul
for FixedUInt<T, N>
{
fn saturating_mul(&self, other: &Self) -> Self {
let res = self.overflowing_mul(&other);
if res.1 {
Self::max_value()
} else {
res.0
}
ret
}
}

Expand Down Expand Up @@ -467,15 +512,13 @@ impl<T: MachineWord, const N: usize> core::ops::Div for FixedUInt<T, N> {
}
}

impl<T: MachineWord, const N: usize> num_traits::CheckedMul for FixedUInt<T, N> {
fn checked_mul(&self, _: &Self) -> Option<Self> {
todo!()
}
}

impl<T: MachineWord, const N: usize> num_traits::CheckedDiv for FixedUInt<T, N> {
fn checked_div(&self, _: &Self) -> Option<Self> {
todo!()
fn checked_div(&self, other: &Self) -> Option<Self> {
if other.is_zero() {
None
} else {
Some(core::ops::Div::<Self>::div(*self, *other))
}
}
}

Expand Down Expand Up @@ -919,15 +962,6 @@ mod tests {
assert_eq!(f.array, [4294967295]);
}

#[test]
fn test_from_doubleword() {
let f = Bn8::from_doubleword(45);
assert_eq!(Some(45), f.to_u32());

let f = Bn8::from_doubleword(256);
assert_eq!(Some(256), f.to_u32());
}

#[test]
fn testsimple() {
assert_eq!(Bn::<u8, 8>::new(), Bn::<u8, 8>::new());
Expand Down
55 changes: 34 additions & 21 deletions src/machineword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ pub trait MachineWord:
+ num_traits::ops::overflowing::OverflowingSub
+ From<u8>
+ num_traits::WrappingShl
+ OverflowingShl
+ OverflowingShr
+ core::fmt::Debug
{
type DoubleWord: num_traits::PrimInt
+ num_traits::Unsigned
+ num_traits::WrappingAdd
+ num_traits::WrappingSub;
+ num_traits::WrappingSub
+ OverflowingShl;
fn to_double(self) -> Self::DoubleWord;
fn from_double(word: Self::DoubleWord) -> Self;

// Todo: get rid of this, single use
fn to_ne_bytes(self) -> [u8; 8];
fn overflowing_shl(self, rhs: u32) -> (Self, bool);
fn overflowing_shr(self, rhs: u32) -> (Self, bool);
}

impl MachineWord for u8 {
Expand All @@ -48,12 +50,6 @@ impl MachineWord for u8 {
ret[0] = self;
ret
}
fn overflowing_shl(self, rhs: u32) -> (u8, bool) {
self.overflowing_shl(rhs)
}
fn overflowing_shr(self, rhs: u32) -> (u8, bool) {
self.overflowing_shr(rhs)
}
}
impl MachineWord for u16 {
type DoubleWord = u32;
Expand All @@ -69,12 +65,6 @@ impl MachineWord for u16 {
halfslice.copy_from_slice(&self.to_ne_bytes());
ret
}
fn overflowing_shl(self, rhs: u32) -> (u16, bool) {
self.overflowing_shl(rhs)
}
fn overflowing_shr(self, rhs: u32) -> (u16, bool) {
self.overflowing_shr(rhs)
}
}
impl MachineWord for u32 {
type DoubleWord = u64;
Expand All @@ -90,14 +80,37 @@ impl MachineWord for u32 {
halfslice.copy_from_slice(&self.to_ne_bytes());
ret
}
fn overflowing_shl(self, rhs: u32) -> (u32, bool) {
self.overflowing_shl(rhs)
}
fn overflowing_shr(self, rhs: u32) -> (u32, bool) {
self.overflowing_shr(rhs)
}
}

// These should be in num_traits
pub trait OverflowingShl: Sized {
fn overflowing_shl(self, rhs: u32) -> (Self, bool);
}
pub trait OverflowingShr: Sized {
fn overflowing_shr(self, rhs: u32) -> (Self, bool);
}

macro_rules! overflowing_shift_impl {
($trait_name:ident, $method:ident, $t:ty) => {
impl $trait_name for $t {
#[inline]
fn $method(self, rhs: u32) -> ($t, bool) {
<$t>::$method(self, rhs)
}
}
};
}

overflowing_shift_impl!(OverflowingShl, overflowing_shl, u8);
overflowing_shift_impl!(OverflowingShl, overflowing_shl, u16);
overflowing_shift_impl!(OverflowingShl, overflowing_shl, u32);
overflowing_shift_impl!(OverflowingShl, overflowing_shl, u64);

overflowing_shift_impl!(OverflowingShr, overflowing_shr, u8);
overflowing_shift_impl!(OverflowingShr, overflowing_shr, u16);
overflowing_shift_impl!(OverflowingShr, overflowing_shr, u32);
overflowing_shift_impl!(OverflowingShr, overflowing_shr, u64);

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit 5fd7d18

Please sign in to comment.