I tried writing an algorithm to simplify a decimal to a fraction and realized it wasn't too simple. Surprisingly I looked online and all the codes I found where either too long, or wouldn't work in some cases. What was even more annoying was that they didn't work for recurring decimals. I was wondering however whether there would be a mathematician/programmer here who understands all the involved processes in simplifying a decimal to a fraction. Anyone?
The algorithm that the other people have given you gets the answer by calculating the Continued Fraction of the number. This gives a fractional sequence which is guaranteed to converge very, very rapidly. However it is not guaranteed to give you the smallest fraction that is within a distance epsilon of a real number. To find that you have to walk the Stern-Brocot tree. To do that you subtract off the floor to get the number in the range [0, 1), then your lower estimate is 0, and your upper estimate is 1. Now do a binary search until you are close enough. At each iteration if your lower is a/b and your upper is c/d your middle is (a+c)/(b+d). Test your middle against x, and either make the middle the upper, the lower, or return your final answer. Here is some very non-idiomatic (and hence, hopefully, readable even if you don't know the language) Python that implements this algorithm.
|
|||||||||
|
I know you said you searched online, but if you missed the following paper it might be of some help. It includes a code example in Pascal. Algorithm To Convert A Decimal To A Fraction* Alternatively, as part of it's standard library, Ruby has code that deals with rational numbers. It can convert from floats to rationals and vice versa. I believe you can look through the code as well. The documentation is found here. I know you're not using Ruby, but it might help to look at the algorithms. Additionally, you can call Ruby code from C# (or even write Ruby code inside a C# code file) if you use IronRuby, which runs on top of the .net framework. *Updated to a new link using http://www.docstoc.com/ as it appears the original URL is no broken (http://homepage.smc.edu/kennedy_john/DEC2FRAC.pdf) |
|||||||||||||||||
|
I found the same paper that Matt referenced, and I took a second and implemented it in Python. Maybe seeing the same idea in code will make it clearer. Granted, you requested an answer in C# and I'm giving it to you in Python, but it's a fairly trivial program, and I'm sure it would be easy to translate. The parameters are
Edit: I just noticed your note about wanting them to work with recurring decimals. I don't know any languages that have syntax to support recurring decimals, so I'm not sure how one would go about handling them, but running 0.6666666 and 0.166666 through this method return the correct results (2/3 and 1/6, respectively). Another Edit (I didn't think this would be so interesting!): If you want to know more about the theory behind this algorithm, Wikipedia has an excellent page on the Euclidian algorithm |
|||||||||||||
|
Here's a C# version of Will Brown's python example. I've also changed it to handle separate whole numbers (e.g. "2 1/8" instead of "17/8").
|
|||
|
You can't represent a recurring decimal in .net so I'll ignore that part of your question. You can only represent a finite and relatively small number of digits. There's an extremely simple algorithm:
so if you have 0.44, you would count 2 places are the decimal point - n = 2, and then write
|
|||||||||||||||||||||
|
I wrote a quick class that runs fairly quick and gives the results I would expect. You can choose your Precision as well. It is much simpler from any code I seen and runs quick as well.
|
|||
|
A recurring decimal can be represented by two finite decimals: the leftward part before the repeat, and the repeating part. E.g. (This elaborates Kirk Broadhurst's answer, which is right as far as it goes, but doesn't cover repeating decimals. I don't promise I made no mistakes above, though I'm confident the general approach works.) |
||||
|
I recently had to perform this very task of working with a Decimal Data Type which is stored in our SQL Server database. At the Presentation Layer this value was edited as a fractional value in a TextBox. The complexity here was working with the Decimal Data Type which holds some pretty large values in comparison to int or long. So to reduce the opportunity for data overrun, I stuck with the Decimal Data Type throughout the conversion. Before I begin, I want to comment on Kirk's previous answer. He is absolutely correct as long as there are no assumptions made. However, if the developer only looks for repeating patterns within the confines of the Decimal Data Type .3333333... can be represented as 1/3. An example of the algorithm can be found at basic-mathematics.com. Again, this means you have to make assumptions based on the information available and using this method only captures a very small subset of repeating decimals. However for small numbers should be okay. Moving forward, let me give you a snapshot of my solution. If you want to read a complete example with additional code I created a blog post with much more detail. Convert Decimal Data Type to a String Fraction
This is pretty straight forward where the DecimalToFraction(decimal value) is nothing more than a simplified entry point for the first method which provides access to all the components which compose a fraction. If you have a decimal of .325 then divide it by 10 to the power of number of decimal places. Lastly reduce the fraction. And, in this example .325 = 325/10^3 = 325/1000 = 13/40. Next, going the other direction. Convert String Fraction to Decimal Data Type
Converting back to a decimal is quite simple as well. Here we parse out the fractional components, store them in something we can work with (here decimal values) and perform our division. |
||||
|
My 2 cents. Here's VB.NET version of btilly's excellent algorithm:
|
|||
|
Well, seems I finally had to do it myself. I just had to create a program simulating the natural way I would solve it myself. I just submitted the code to codeproject as writing out the whole code here won't be suitable. You can download the project from here Fraction_Conversion, or look at the codeproject page here. Here's how it works:
Code Preview:
Thanks @ Darius for given me an idea of how to solve the recurring decimals :) |
|||||||||||||||||
|
This algorithm by David Eppstein, UC Irvine, based on the theory of continued fractions and originally in C, was translated to C# by me. The fractions it generates satisfy the error margin but mostly do not look as good as the solution in my other answer. E.g. It is definitely fast though. There is an overload to specify the error margin as a double (relative to the value, not the absolute error). For the By the way, if your fractions can get large, change the
Test results:
|
||||
|
I implemented btilly's answer in C# and added support for negative numbers.
The
I tested
As you can see, values near Another solution would be to, as soon as the number of iterations reaches a certain amount, like 10 or 20, abort and call another algorithm. Actually the algorithm in my other answer is a lot faster. By the way, if your fractions can get large, change the |
||||
|
Here's an algorithm implemented in VB that converts Floating Point Decimal to Integer Fraction that I wrote many years ago. Basically you start with a numerator = 0 and a denominator = 1, then if the quotient is less than the decimal input, add 1 to the numerator and if the quotient is greater than the decimal input, add 1 to the denominator. Repeat until you get within your desired precision. |
|||
|
If I were you I'd handle the "no repeating decimals in .NET" problem by having it convert strings with the recurrence marked somehow. E.g. 1/3 could be represented "0.R3" 1/60 could be represented "0.01R6" I'd require an explicit cast from double or decimal because such values could only be converted into a fraction that was close. Implicit cast from int is ok. You could use a struct and store your fraction (f) in two longs p and q such that f=p/q, q!=0, and gcd(p, q) == 1. |
|||
|
0.333333...
as1/3
for example? – Eelvex Feb 26 '11 at 2:58