-
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
Conversation
It seems this is nice to have but needs to work very differently:
Also, it might make sense to preserve the sign of floating point zeros by making Ratio::reduce no longer force the denominator to be positive, but I'm not sure if this causes issues elsewhere. |
Returning |
Some googling suggests that an algorithm like http://www.math.niu.edu/~rusin/known-math/95/rationalize might be better. It uses continued fractions, which would imply that it's going to be close to the closest rational to the input float for a given upper bound on the denominator; I would suggest that allowing specifying an optional upper bound would be good too, either via |
I'm sorry for not making any progress on this, I was just too busy with work. I'll try to get something done next week. Thanks @huonw for the link, I'll have a look. |
I did a bit of research last week about how other programming languages deal with float to ratio conversion. I let them ran with 0.1 and 3.14159265359: The results were:
Lisp (SBCL), GMP (through Python binding):
Note that Lisp has the distinction between I currently learn towards following the Lisp route and implementing a I'm sorry that I don't remember the name, but I'd like to thank the person that gave me the idea of looking into the Lisp implementation (it was at the last Rust meetup in Mountain View). |
@vmx What's the status of this -- are you stuck on anything? |
@catamorphism Thanks for asking. I'm not really stuck. I've implemented the algorithm from Lisp in Python to get a better understanding. Next step is porting it to Rust. I'd expect to have a version by the end of the week (rebased on top of the newest master of course). |
I've create a Gist with an implementation of rationalize. It seems to work. Tests and proper integration with libextra is still missing, but I thought I post my progress. Once done I'll add it properly to this pull request. |
That looks cool, although the shifts seems to be prone to overflow, e.g. changing Also, |
I haven't found the time to integrate it into Rust yet, but I updated my Gist according to the comments from @huonw. According to the Rust manual are |
I did a force push to my branch as the first version wasn't really going anywhere (if you know a way to preserve the old commits of a pull request and adding a completely unrelated branch, let me know). I dared to have the current commit based on a slightly outdated Rust version and I also only compiled/tested it with I would move integer_decode_float() to libstd f32/f64, though I'm not sure if that's the way to go, please let me know. |
Currently the code doesn't handle |
The `integer_decode()` function decodes a float (f32/f64) into integers containing the mantissa, exponent and sign. It's needed for `rationalize()` implementation of rust-lang#9838. The code got ported from ABCL [1]. [1] http://abcl.org/trac/browser/trunk/abcl/src/org/armedbear/lisp/FloatFunctions.java?rev=14465#L94
The `integer_decode()` function decodes a float (f32/f64) into integers containing the mantissa, exponent and sign. It's needed for `rationalize()` implementation of #9838. The code got ported from ABCL [1]. [1] http://abcl.org/trac/browser/trunk/abcl/src/org/armedbear/lisp/FloatFunctions.java?rev=14465#L94 I got the permission to use this code for Rust from Peter Graves (the ABCL copyright holder) . If there's any further IP clearance needed, let me know.
rationalize transforms a float into a Ratio<BigInt>. It's based on the Common Lisp implementation.
Another update of my commit. It now used the already merged I've left the comments where the algorithm comes from as I haven't received any permission to use it yet (And I think that's needed as the code is non-trivial). Other than that I think it's ready to have a closer review. |
if mantissa == 0 || exponent >= 0 { | ||
let mut numer: BigUint = FromPrimitive::from_u64(mantissa).unwrap(); | ||
numer = numer << (exponent as uint); | ||
let bigintSign: Sign = if sign == 1 { Plus } else { Minus }; |
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.
Convention would be bigint_sign
.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
A power of two should definitely be an even number, and 2**-100
is representable exactly as a float, so there shouldn't be any loses. (Specifically, this should be an exact flip of the 2f64.pow(&100.)
case.)
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, 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 <float> <numerator> % <denominator>
.)
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.
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 comment
The 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 2.0**-100
isn't exact for f64
(it appears to be exactly one epsilon off); I guess we don't have much choice?
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.
Yes, it's one epsilon off. When you look at the bit representation:
>>> import struct
>>> [struct.unpack('<Q', struct.pack('<d', f))[0] for f in (2 ** -100, 1.0/1267650600228229260759214850049, 1.0/1267650600228229401496703205376)]
[4156822456062967808, 4156822456062967809, 4156822456062967808]
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 comment
The 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 comment
The 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.
There are three reasons for merging #11125 instead of this one:
|
I like that reasoning. (If we want, we can add simplification methods to |
Closing in favor of #11125, because it sounds like that one should be merged over this one (but feel free to reopen if I'm wrong) |
The Ratio::from_float() converts a float (f32 and f64) into a Ratio<BigInt>. Closes rust-lang#9838
The Ratio::rational() converts a float (f32 and f64) into a Ratio<BigInt>. Closes #9838
Add a function to create a Ratio out of a float
(f32 or f64).
I'm a newbie, hence this commit is meant as a first step to get something similar committed. I originally tried to port the
as_integer_ratio()
function from CPython, but then found out that the IronPython's implementation is even simpler.So please let me know what needs to be done to make that a committable change.