Skip to content

Commit 9115df6

Browse files
authored
Merge branch 'master' into missing-impls-for-wrapping
2 parents 9d8c808 + 51e9555 commit 9115df6

File tree

2 files changed

+208
-2
lines changed

2 files changed

+208
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
A collection of numeric types and traits for Rust.
44

55
This includes new types for big integers, rationals, and complex numbers,
6-
new traits for generic programming on numeric properties like `Integer,
6+
new traits for generic programming on numeric properties like `Integer`,
77
and generic range iterators.
88

99
[Documentation](http://rust-num.github.io/num)

rational/src/lib.rs

Lines changed: 207 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use std::str::FromStr;
3636
use bigint::{BigInt, BigUint, Sign};
3737

3838
use integer::Integer;
39-
use traits::{FromPrimitive, Float, PrimInt, Num, Signed, Zero, One};
39+
use traits::{FromPrimitive, Float, PrimInt, Num, Signed, Zero, One, Bounded, NumCast};
4040

4141
/// Represents the ratio between 2 numbers.
4242
#[derive(Copy, Clone, Hash, Debug)]
@@ -668,6 +668,179 @@ impl RatioErrorKind {
668668
}
669669
}
670670

671+
impl FromPrimitive for Ratio<BigInt> {
672+
fn from_i64(n: i64) -> Option<Self> {
673+
Some(Ratio::from_integer(n.into()))
674+
}
675+
676+
fn from_u64(n: u64) -> Option<Self> {
677+
Some(Ratio::from_integer(n.into()))
678+
}
679+
680+
fn from_f32(n: f32) -> Option<Self> {
681+
Ratio::from_float(n)
682+
}
683+
684+
fn from_f64(n: f64) -> Option<Self> {
685+
Ratio::from_float(n)
686+
}
687+
}
688+
689+
macro_rules! from_primitive_integer {
690+
($typ:ty, $approx:ident) => {
691+
impl FromPrimitive for Ratio<$typ> {
692+
fn from_i64(n: i64) -> Option<Self> {
693+
<$typ as FromPrimitive>::from_i64(n).map(Ratio::from_integer)
694+
}
695+
696+
fn from_u64(n: u64) -> Option<Self> {
697+
<$typ as FromPrimitive>::from_u64(n).map(Ratio::from_integer)
698+
}
699+
700+
fn from_f32(n: f32) -> Option<Self> {
701+
$approx(n, 10e-20, 30)
702+
}
703+
704+
fn from_f64(n: f64) -> Option<Self> {
705+
$approx(n, 10e-20, 30)
706+
}
707+
}
708+
}
709+
}
710+
711+
from_primitive_integer!(i8, approximate_float);
712+
from_primitive_integer!(i16, approximate_float);
713+
from_primitive_integer!(i32, approximate_float);
714+
from_primitive_integer!(i64, approximate_float);
715+
from_primitive_integer!(isize, approximate_float);
716+
717+
from_primitive_integer!(u8, approximate_float_unsigned);
718+
from_primitive_integer!(u16, approximate_float_unsigned);
719+
from_primitive_integer!(u32, approximate_float_unsigned);
720+
from_primitive_integer!(u64, approximate_float_unsigned);
721+
from_primitive_integer!(usize, approximate_float_unsigned);
722+
723+
impl<T: Integer + Signed + Bounded + NumCast + Clone> Ratio<T> {
724+
pub fn approximate_float<F: Float + NumCast>(f: F) -> Option<Ratio<T>> {
725+
// 1/10e-20 < 1/2**32 which seems like a good default, and 30 seems
726+
// to work well. Might want to choose something based on the types in the future, e.g.
727+
// T::max().recip() and T::bits() or something similar.
728+
let epsilon = <F as NumCast>::from(10e-20).expect("Can't convert 10e-20");
729+
approximate_float(f, epsilon, 30)
730+
}
731+
}
732+
733+
fn approximate_float<T, F>(val: F, max_error: F, max_iterations: usize) -> Option<Ratio<T>>
734+
where T: Integer + Signed + Bounded + NumCast + Clone,
735+
F: Float + NumCast
736+
{
737+
let negative = val.is_sign_negative();
738+
let abs_val = val.abs();
739+
740+
let r = approximate_float_unsigned(abs_val, max_error, max_iterations);
741+
742+
// Make negative again if needed
743+
if negative {
744+
r.map(|r| r.neg())
745+
} else {
746+
r
747+
}
748+
}
749+
750+
// No Unsigned constraint because this also works on positive integers and is called
751+
// like that, see above
752+
fn approximate_float_unsigned<T, F>(val: F, max_error: F, max_iterations: usize) -> Option<Ratio<T>>
753+
where T: Integer + Bounded + NumCast + Clone,
754+
F: Float + NumCast
755+
{
756+
// Continued fractions algorithm
757+
// http://mathforum.org/dr.math/faq/faq.fractions.html#decfrac
758+
759+
if val < F::zero() {
760+
return None;
761+
}
762+
763+
let mut q = val;
764+
let mut n0 = T::zero();
765+
let mut d0 = T::one();
766+
let mut n1 = T::one();
767+
let mut d1 = T::zero();
768+
769+
let t_max = T::max_value();
770+
let t_max_f = match <F as NumCast>::from(t_max.clone()) {
771+
None => return None,
772+
Some(t_max_f) => t_max_f,
773+
};
774+
775+
// 1/epsilon > T::MAX
776+
let epsilon = t_max_f.recip();
777+
778+
// Overflow
779+
if q > t_max_f {
780+
return None;
781+
}
782+
783+
for _ in 0..max_iterations {
784+
let a = match <T as NumCast>::from(q) {
785+
None => break,
786+
Some(a) => a,
787+
};
788+
789+
let a_f = match <F as NumCast>::from(a.clone()) {
790+
None => break,
791+
Some(a_f) => a_f,
792+
};
793+
let f = q - a_f;
794+
795+
// Prevent overflow
796+
if !a.is_zero() &&
797+
(n1 > t_max.clone() / a.clone() ||
798+
d1 > t_max.clone() / a.clone() ||
799+
a.clone() * n1.clone() > t_max.clone() - n0.clone() ||
800+
a.clone() * d1.clone() > t_max.clone() - d0.clone()) {
801+
break;
802+
}
803+
804+
let n = a.clone() * n1.clone() + n0.clone();
805+
let d = a.clone() * d1.clone() + d0.clone();
806+
807+
n0 = n1;
808+
d0 = d1;
809+
n1 = n.clone();
810+
d1 = d.clone();
811+
812+
// Simplify fraction. Doing so here instead of at the end
813+
// allows us to get closer to the target value without overflows
814+
let g = Integer::gcd(&n1, &d1);
815+
if !g.is_zero() {
816+
n1 = n1 / g.clone();
817+
d1 = d1 / g.clone();
818+
}
819+
820+
// Close enough?
821+
let (n_f, d_f) = match (<F as NumCast>::from(n), <F as NumCast>::from(d)) {
822+
(Some(n_f), Some(d_f)) => (n_f, d_f),
823+
_ => break,
824+
};
825+
if (n_f / d_f - val).abs() < max_error {
826+
break;
827+
}
828+
829+
// Prevent division by ~0
830+
if f < epsilon {
831+
break;
832+
}
833+
q = f.recip();
834+
}
835+
836+
// Overflow
837+
if d1.is_zero() {
838+
return None;
839+
}
840+
841+
Some(Ratio::new(n1, d1))
842+
}
843+
671844
#[cfg(test)]
672845
fn hash<T: hash::Hash>(x: &T) -> u64 {
673846
use std::hash::Hasher;
@@ -684,6 +857,7 @@ mod test {
684857

685858
use std::str::FromStr;
686859
use std::i32;
860+
use std::f64;
687861
use traits::{Zero, One, Signed, FromPrimitive, Float};
688862

689863
pub const _0: Rational = Ratio {
@@ -774,6 +948,38 @@ mod test {
774948
let _a = Ratio::new(1, 0);
775949
}
776950

951+
#[test]
952+
fn test_approximate_float() {
953+
assert_eq!(Ratio::from_f32(0.5f32), Some(Ratio::new(1i64, 2)));
954+
assert_eq!(Ratio::from_f64(0.5f64), Some(Ratio::new(1i32, 2)));
955+
assert_eq!(Ratio::from_f32(5f32), Some(Ratio::new(5i64, 1)));
956+
assert_eq!(Ratio::from_f64(5f64), Some(Ratio::new(5i32, 1)));
957+
assert_eq!(Ratio::from_f32(29.97f32), Some(Ratio::new(2997i64, 100)));
958+
assert_eq!(Ratio::from_f32(-29.97f32), Some(Ratio::new(-2997i64, 100)));
959+
960+
assert_eq!(Ratio::<i8>::from_f32(63.5f32), Some(Ratio::new(127i8, 2)));
961+
assert_eq!(Ratio::<i8>::from_f32(126.5f32), Some(Ratio::new(126i8, 1)));
962+
assert_eq!(Ratio::<i8>::from_f32(127.0f32), Some(Ratio::new(127i8, 1)));
963+
assert_eq!(Ratio::<i8>::from_f32(127.5f32), None);
964+
assert_eq!(Ratio::<i8>::from_f32(-63.5f32), Some(Ratio::new(-127i8, 2)));
965+
assert_eq!(Ratio::<i8>::from_f32(-126.5f32), Some(Ratio::new(-126i8, 1)));
966+
assert_eq!(Ratio::<i8>::from_f32(-127.0f32), Some(Ratio::new(-127i8, 1)));
967+
assert_eq!(Ratio::<i8>::from_f32(-127.5f32), None);
968+
969+
assert_eq!(Ratio::<u8>::from_f32(-127f32), None);
970+
assert_eq!(Ratio::<u8>::from_f32(127f32), Some(Ratio::new(127u8, 1)));
971+
assert_eq!(Ratio::<u8>::from_f32(127.5f32), Some(Ratio::new(255u8, 2)));
972+
assert_eq!(Ratio::<u8>::from_f32(256f32), None);
973+
974+
assert_eq!(Ratio::<i64>::from_f64(-10e200), None);
975+
assert_eq!(Ratio::<i64>::from_f64(10e200), None);
976+
assert_eq!(Ratio::<i64>::from_f64(f64::INFINITY), None);
977+
assert_eq!(Ratio::<i64>::from_f64(f64::NEG_INFINITY), None);
978+
assert_eq!(Ratio::<i64>::from_f64(f64::NAN), None);
979+
assert_eq!(Ratio::<i64>::from_f64(f64::EPSILON), Some(Ratio::new(1, 4503599627370496)));
980+
assert_eq!(Ratio::<i64>::from_f64(0.0), Some(Ratio::new(0, 1)));
981+
assert_eq!(Ratio::<i64>::from_f64(-0.0), Some(Ratio::new(0, 1)));
982+
}
777983

778984
#[test]
779985
fn test_cmp() {

0 commit comments

Comments
 (0)