Skip to content

Commit

Permalink
Merge pull request #72 from Maaarcocr/main
Browse files Browse the repository at this point in the history
Add support for Add, Sub, Mul & Div ops for Integer
  • Loading branch information
matsadler authored Aug 17, 2023
2 parents e6ab589 + 881329b commit 7ac380b
Show file tree
Hide file tree
Showing 2 changed files with 401 additions and 2 deletions.
193 changes: 191 additions & 2 deletions src/integer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
use std::fmt;
use std::{
fmt,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
os::raw::c_long,
};

use rb_sys::{rb_ll2inum, rb_to_int, rb_ull2inum, ruby_special_consts, ruby_value_type, VALUE};
use rb_sys::{
rb_big_cmp, rb_big_div, rb_big_eq, rb_big_minus, rb_big_mul, rb_big_norm, rb_big_plus,
rb_int2big, rb_ll2inum, rb_to_int, rb_ull2inum, ruby_special_consts, ruby_value_type, Qtrue,
VALUE,
};

use crate::{
error::{protect, Error},
Expand Down Expand Up @@ -477,6 +485,36 @@ impl Integer {
IntegerType::Bignum(big) => big.to_usize(),
}
}

/// Normalize `self`. If `self` is a `Fixnum`, returns `self`. If `self` is
/// a `Bignum`, if it is small enough to fit in a `Fixnum`, returns a
/// `Fixnum` with the same value. Otherwise, returns `self`.
pub fn norm(&self) -> Self {
match self.integer_type() {
IntegerType::Fixnum(_) => *self,
IntegerType::Bignum(big) => unsafe {
Integer::from_rb_value_unchecked(rb_big_norm(big.as_rb_value()))
},
}
}

fn binary_operation_visit<T>(
&self,
other: &Self,
rust_op: fn(Fixnum, Fixnum) -> T,
ruby_op: fn(VALUE, VALUE) -> T,
) -> T {
match self.integer_type() {
IntegerType::Bignum(a) => ruby_op(a.as_rb_value(), other.as_rb_value()),
IntegerType::Fixnum(a) => match other.integer_type() {
IntegerType::Bignum(b) => {
let a = unsafe { rb_int2big(a.to_isize()) };
ruby_op(a, b.as_rb_value())
}
IntegerType::Fixnum(b) => rust_op(a, b),
},
}
}
}

impl fmt::Display for Integer {
Expand Down Expand Up @@ -515,3 +553,154 @@ impl TryConvert for Integer {
}
}
}

impl PartialEq for Integer {
fn eq(&self, other: &Self) -> bool {
match self.integer_type() {
IntegerType::Bignum(a) => unsafe {
rb_big_eq(a.as_rb_value(), other.as_rb_value()) == Qtrue.into()
},
IntegerType::Fixnum(a) => a.as_rb_value() == other.norm().as_rb_value(),
}
}
}

impl PartialOrd for Integer {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.binary_operation_visit(
other,
|a, b| (a.as_rb_value() as c_long).partial_cmp(&(b.as_rb_value() as c_long)),
|a, b| unsafe {
let result = rb_big_cmp(a, b);
Integer::from_rb_value_unchecked(result)
.to_i64()
.unwrap()
.partial_cmp(&0)
},
)
}
}

impl Add for Integer {
type Output = Self;

fn add(self, other: Self) -> Self {
self.binary_operation_visit(
&other,
|a, b| {
let raw_a = a.as_rb_value() as c_long;
let raw_b = b.as_rb_value() as c_long;
let result = raw_a.checked_add(raw_b).and_then(|i| i.checked_sub(1));
if let Some(result) = result {
unsafe { Integer::from_rb_value_unchecked(result as VALUE) }
} else {
let a = unsafe { rb_int2big(a.to_isize()) };
let result = unsafe { rb_big_plus(a, b.as_rb_value()) };
unsafe { Integer::from_rb_value_unchecked(result) }
}
},
|a, b| {
let result = unsafe { rb_big_plus(a, b) };
unsafe { Integer::from_rb_value_unchecked(result) }
},
)
}
}

impl AddAssign for Integer {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}

impl Sub for Integer {
type Output = Self;

fn sub(self, other: Self) -> Self {
self.binary_operation_visit(
&other,
|a, b| {
let raw_a = a.as_rb_value() as c_long;
let raw_b = b.as_rb_value() as c_long;
let result = raw_a.checked_sub(raw_b).and_then(|i| i.checked_add(1));
if let Some(result) = result {
unsafe { Integer::from_rb_value_unchecked(result as VALUE) }
} else {
let a = unsafe { rb_int2big(a.to_isize()) };
let result = unsafe { rb_big_minus(a, b.as_rb_value()) };
unsafe { Integer::from_rb_value_unchecked(result) }
}
},
|a, b| {
let result = unsafe { rb_big_minus(a, b) };
unsafe { Integer::from_rb_value_unchecked(result) }
},
)
}
}

impl SubAssign for Integer {
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}

impl Mul for Integer {
type Output = Self;

fn mul(self, other: Self) -> Self {
self.binary_operation_visit(
&other,
|a, b| {
let raw_a = a.to_i64();
let raw_b = b.to_i64();
let result = raw_a.checked_mul(raw_b);
if let Some(result) = result {
Integer::from_i64(result)
} else {
let a = unsafe { rb_int2big(a.to_isize()) };
let result = unsafe { rb_big_mul(a, b.as_rb_value()) };
unsafe { Integer::from_rb_value_unchecked(result) }
}
},
|a, b| {
let result = unsafe { rb_big_mul(a, b) };
unsafe { Integer::from_rb_value_unchecked(result) }
},
)
}
}

impl MulAssign for Integer {
fn mul_assign(&mut self, other: Self) {
*self = *self * other;
}
}

impl Div for Integer {
type Output = Self;

fn div(self, other: Self) -> Self {
self.binary_operation_visit(
&other,
|a, b| {
let raw_a = a.to_i64();
let raw_b = b.to_i64();
// the only case when division can overflow is when dividing
// i64::MIN by -1, but Fixnum can't represent that I64::MIN
// so we can safely not use checked_div here
Integer::from_i64(raw_a / raw_b)
},
|a, b| {
let result = unsafe { rb_big_div(a, b) };
unsafe { Integer::from_rb_value_unchecked(result) }
},
)
}
}

impl DivAssign for Integer {
fn div_assign(&mut self, other: Self) {
*self = *self / other;
}
}
Loading

0 comments on commit 7ac380b

Please sign in to comment.