-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add float to Ratio conversion #9838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,7 @@ | |
use std::cmp; | ||
use std::from_str::FromStr; | ||
use std::num::{Zero,One,ToStrRadix,FromStrRadix,Round}; | ||
use super::bigint::BigInt; | ||
use super::bigint::{BigInt, BigUint, Sign, Plus, Minus}; | ||
|
||
/// Represents the ratio between 2 numbers. | ||
#[deriving(Clone)] | ||
|
@@ -107,6 +107,66 @@ impl<T: Clone + Integer + Ord> | |
} | ||
} | ||
|
||
impl Ratio<BigInt> { | ||
// Ported from | ||
// http://www.ginac.de/CLN/cln.git/?p=cln.git;a=blob;f=src/real/misc/cl_R_rationalize.cc;hb=HEAD | ||
// http://sourceforge.net/p/sbcl/sbcl/ci/master/tree/src/code/float.lisp#l850 | ||
// (2013-11-17) | ||
/// Converts a float into a rational number | ||
fn rationalize<T: Float>(f: T) -> Option<BigRational> { | ||
if !f.is_finite() { | ||
return None; | ||
} | ||
let (mantissa, exponent, sign) = f.integer_decode(); | ||
if mantissa == 0 || exponent >= 0 { | ||
let mut numer: BigUint = FromPrimitive::from_u64(mantissa).unwrap(); | ||
numer = numer << (exponent as uint); | ||
let bigint_sign: Sign = if sign == 1 { Plus } else { Minus }; | ||
return Some(Ratio::from_integer(BigInt::from_biguint(bigint_sign, numer))); | ||
} | ||
|
||
let one: BigInt = One::one(); | ||
let mut a: BigRational = Ratio::new( | ||
FromPrimitive::from_u64(2 * mantissa - 1).unwrap(), | ||
one << ((1 - exponent) as uint)); | ||
let mut b: BigRational = Ratio::new( | ||
FromPrimitive::from_u64(2 * mantissa + 1).unwrap(), | ||
one << ((1 - exponent) as uint)); | ||
|
||
let mut p0: BigInt = Zero::zero(); | ||
let mut p1: BigInt = One::one(); | ||
let mut q0: BigInt = One::one(); | ||
let mut q1: BigInt = Zero::zero(); | ||
let mut c; | ||
|
||
loop { | ||
c = a.ceil(); | ||
if c >= b { | ||
let k = c.to_integer() - One::one(); | ||
|
||
let p2 = k * p1 + p0; | ||
p0 = p1; | ||
p1 = p2; | ||
let q2 = k * q1 + q0; | ||
q0 = q1; | ||
q1 = q2; | ||
|
||
let tmp = (b - Ratio::from_integer(k.clone())).recip(); | ||
b = (a - Ratio::from_integer(k)).recip(); | ||
a = tmp; | ||
} else { | ||
break; | ||
} | ||
}; | ||
|
||
let p_last: BigInt = c.to_integer() * p1 + p0; | ||
let q_last: BigInt = c.to_integer() * q1 + q0; | ||
let p = if sign == 1 { p_last } else { -p_last }; | ||
Some(Ratio::new(p, q_last)) | ||
} | ||
} | ||
|
||
|
||
/* Comparisons */ | ||
|
||
// comparing a/b and c/d is the same as comparing a*d and b*c, so we | ||
|
@@ -621,4 +681,43 @@ mod test { | |
test(s); | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_rationalize() { | ||
fn test<T: Float>(given: T, expected: (&str, &str)) { | ||
let ratio: BigRational = Ratio::rationalize(given).unwrap(); | ||
let (numer, denom) = expected; | ||
assert_eq!(ratio, Ratio::new( | ||
FromStr::from_str(numer).unwrap(), | ||
FromStr::from_str(denom).unwrap())); | ||
} | ||
|
||
// f32 | ||
test(3.14159265359f32, ("93343", "29712")); | ||
test(2f32.pow(&100.), ("1267650600228229401496703205376", "1")); | ||
test(-2f32.pow(&100.), ("-1267650600228229401496703205376", "1")); | ||
test(1.0 / 2f32.pow(&100.), ("1", "1267650524670370179181738721296")); | ||
test(684729.48391f32, ("1369459", "2")); | ||
test(-8573.5918555f32, ("-420106", "49")); | ||
|
||
// f64 | ||
test(3.14159265359f64, ("226883371", "72219220")); | ||
test(2f64.pow(&100.), ("1267650600228229401496703205376", "1")); | ||
test(-2f64.pow(&100.), ("-1267650600228229401496703205376", "1")); | ||
test(1.0 / 2f64.pow(&100.), ("1", "1267650600228229260759214850049")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A power of two should definitely be an even number, and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, also, I get different numbers in Haskell (and Python agrees from a incomplete check): > -- f32
> let nums = [3.14159265359, 2.0^100, -2.0 ^ 100, 1.0 / (2.0^100), 684729.48391, -8573.5918555] :: [Float]
> mapM_ (\n -> putStrLn $ show n ++ " \t" ++ show (toRational n)) nums
3.1415927 13176795 % 4194304
1.2676506e30 1267650600228229401496703205376 % 1
-1.2676506e30 (-1267650600228229401496703205376) % 1
7.888609e-31 1 % 1267650600228229401496703205376
684729.5 1369459 % 2
-8573.592 (-4389679) % 512
> -- f64
> let nums = [3.14159265359, 2.0^100, -2.0 ^ 100, 1.0 / (2.0^100), 684729.48391, -8573.5918555] :: [Double]
> mapM_ (\n -> putStrLn $ show n ++ "\t" ++ show (toRational n)) nums
3.14159265359 3537118876014453 % 1125899906842624
1.2676506002282294e30 1267650600228229401496703205376 % 1
-1.2676506002282294e30 (-1267650600228229401496703205376) % 1
7.888609052210118e-31 1 % 1267650600228229401496703205376
684729.48391 367611342500051 % 536870912
-8573.5918555 (-4713381968463931) % 549755813888 (The format is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Python and Haskell have a different implementation. They do the simple way. The algorithm I use tries to find the smallest possible value that still matches the float. Try Lisp (Clisp or SBCL), that's what I've used to verify my numbers. Let me write a small script to show the output. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see (sorry for my clisp inexperience): > (map nil #'(lambda (n) (format t "~S ~S~%" n (rationalize n))) `(3.14159265359d0 684729.48391d0 -8573.5918555d0 ,(expt 2.0d0 -100)))
3.14159265359d0 226883371/72219220
684729.48391d0 68472948391/100000
-8573.5918555d0 -13743853556/1603045
7.888609052210118d-31 1/1267650600228229260759214850049
NIL I think it's a little peculiar that the exact There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's one epsilon off. When you look at the bit representation:
We have a choice in fixing the algorithm. Not sure how easy that would be as I've only ported it and not really thought through it. I'll give it a try though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming can you get the license stuff OK-d, I'm happy to r+ as is, and we can investigate a perfectly precise algorithm later, if necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've sent a mail on Monday, still waiting for a reply :) Though I'll take the chance to have a closer look at the algorithm. I'm not sure if it makes sense to have it go in with a known bug. |
||
test(684729.48391f64, ("68472948391", "100000")); | ||
test(-8573.5918555f64, ("-13743853556", "1603045")); | ||
} | ||
|
||
#[test] | ||
fn test_rationalize_fail() { | ||
use std::{f32, f64}; | ||
|
||
assert_eq!(Ratio::rationalize(f32::NAN), None); | ||
assert_eq!(Ratio::rationalize(f32::INFINITY), None); | ||
assert_eq!(Ratio::rationalize(f32::NEG_INFINITY), None); | ||
assert_eq!(Ratio::rationalize(f64::NAN), None); | ||
assert_eq!(Ratio::rationalize(f64::INFINITY), None); | ||
assert_eq!(Ratio::rationalize(f64::NEG_INFINITY), None); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this just be
let c = a.ceil();
in the loop?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Oh, no, I missed that it's used below.)