Click here to Skip to main content
Licence 
First Posted 15 Jan 2005
Views 64,443
Bookmarked 12 times

Decimal to English Fraction Algorithm

By | 23 Jan 2005 | Article
A four line algorithm in MC++ for converting decimals to fractions.

Introduction

A four line algorithm in MC++ for converting a decimal value to three separate pieces: units, numerator, denominator. Their results are suitable to format a string.

Formula

The basic formula is to divide the right side of the decimal value by the decimal equivalent of the fractional measure and round to an integer. This becomes the numerator over the desired denominator (of the conversion fraction). Thus, for converting to an eighth, as 1/8 is .125, one divides the IEEERmainder() of the decimal by .125 to obtain a numerator over 8.

Convert 3.6742 to 16th: .6742/.0625 = 10.7872. 10.7872 rounds to 11 creating the fraction 11/16. The result is three and eleven sixteen (3 11/16).

Convert 3.6742 to 8th: .6742/.125 = 5.3936 = 5/8. The result is three and five eighth. (3 5/8).

I use Math::IEERemainder(decimal, 1.0) to separate the decimal from the single value, and Math::floor(decimal) to separate the units. If the decimal part is greater than .5 then the Math algorithm returns a negative complement, and must be subtracted from one. Thus the line:

if (Math::Sign(remainder) == -1) remainder = 1 + remainder

.6742 returns -0.32579942, which, when added to 1.0 results in .6742008.

Due to this check of the sign the algorithm only works for positive numbers.

The Algorithm:

(The decimal and denominator are input)

Single remainder = Math::IEEERemainder(decimal, 1.0);
if (Math::Sign(remainder) == -1) remainder = 1 + remainder;
Int32  units = Convert::ToInt32(Math::Floor(decimal));
Int32  numerator = Convert::ToInt32(Math::Round(remainder/denominator));

Other considerations;

For flexibility, one would prefer to provide an integer specifying the desired conversion rather than hard code, say, .125 as the denominator. Thus input an integer numerator and compute the divisor.

// compute the fractions numerator
Single divisor = Convert::ToSingle(1)/Convert::ToSingle(denominator);
Int32 numerator = Convert::ToInt32(Math::Round(remainder/divisor));

The algorithm only works for positive decimals, thus one needs to test for flag, correct and restore negativity. Further, problems that need to be considered are the rounding down to zero and rounding up to the next unit, and reduction of the fraction. The following code accounts for these.

The following code was written for a very specific purpose: to convert English inch measurement fractions, specifically, the common fractions 1/8, 1/4 and 1/2. (Although I tested to 1/32.) I was not interested in fractions like 1/5 or 1/7 or 1/324 whatever. The algorithm may be useful to help those, but not the example function. The code is not generalized. But the algorithm is. The code is only provided as a wrapper example.

Code:

#pragma warning( disable : 4244 )  // possible loss of data due to conversion
// Convert a Single to a string fraction of the 
// form "integer numerator/denominator"
String* Utils::Form1::SingleToStringFraction(Single decimal, Int32 denominator)
{
   // Input must be positive so save and restore the negative if necessary.
   bool isneg = (Math::Sign(decimal) == -1) ? true : false;
   if (isneg) decimal *= -1;

   // obtain the decimal and units parts of the input number
   Single remainder = Math::IEEERemainder(decimal, 1.0);
   if (Math::Sign(remainder) == -1) remainder = 1 + remainder;
   Int32  units = Convert::ToInt32(Math::Floor(decimal));

   // compute the fractions numerator
   Single divisor = Convert::ToSingle(1)/Convert::ToSingle(denominator);
   Int32 numerator = Convert::ToInt32(Math::Round(remainder/divisor));

   String* fraction;

   // Handle an error or condition or reduce the fraction
    // and convert to a string for the return.
   if ((numerator > 0) && (numerator == denominator))
   {
      // than fraction is one full unit
      units++;
      fraction = S"";
   }
   else if (numerator == 0)
   {
      // as numerator is 0, no fraction
      fraction = S"";
   }
   else
   {
      // reduce
      while (numerator%2 == 0)
      {
         numerator   /= 2;
         denominator /= 2;
      }
      fraction = String::Format(" {0}/{1}",
                   numerator.ToString(), denominator.ToString());
   }

   // restore negativity
   if (isneg) units *= -1;

#ifdef _DEBUG_CUT
   String* rtnstr;
   if (isneg) decimal *= -1;
   rtnstr = String::Format("{0}{1}", units.ToString(), fraction);
   Diagnostics::Trace::WriteLine(rtnstr, decimal.ToString());
#endif

   return String::Format("{0}{1}", units.ToString(), fraction);
}
#pragma warning( default : 4244 )

Caveat

I have never claimed to know everything. And, my MC++ skills may be lacking. If you know of a better and more efficient algorithm, or can improve on the quality of the above code, please comment. In the program that I am working on, I will be going back and forth from fractions to decimals regularly. Efficiency would be nice.

Oh yes, one could simply convert to a string and use split on S"."; but what fun in that? And using Math to split the Single qualifies as an algorithm while splitting a string does not.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Gammill

Retired

United States United States

Member

Retired C programmer and Unix Sys Admin, then VC6 C++ MFC programmer. I moved to VC7 C++ 2003 in Oct of 04, and VC8 C++/CLI early in 06. I resisted C# for a long time. But now C# is my preferred language. I'm through with upgrading. I'll stay at VC2008 and C# as I only program for fun anymore.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 Pinmembermanoj kumar choubey0:28 14 Mar '12  
GeneralBase 2 fractions PinmemberJaen13:46 18 Jun '05  
GeneralA few points PinmemberChris Hills23:35 17 Jan '05  
GeneralRe: A few points PinmemberGammill9:46 18 Jan '05  
GeneralSingle limits to only 7 digits PinmemberGammill19:00 16 Jan '05  
GeneralOthers of set of 3 PinmemberGammill12:10 16 Jan '05  
GeneralRe: Others of set of 3 PinmemberWoR23:45 16 Jan '05  
GeneralRe: Others of set of 3 PinmemberGammill14:10 17 Jan '05  
GeneralRe: Others of set of 3 Pinmemberrvdt20:33 25 Jan '05  
GeneralDecimal and Floating-point to fractions is never easy. PinsussMarc Brooks12:21 24 Jan '05  
I had significant input to the Fraction class Here. You're much better off representing Doubles and Decimals as repeated fractions and then simplifying. Here's the basic conversion that recognizes the special values:
public static Fraction ToFraction(double inValue)
{
	// it's one of the indeterminates... which?
	if (double.IsNaN(inValue))
		return NaN;
	else if (double.IsNegativeInfinity(inValue))
		return NegativeInfinity;
	else if (double.IsPositiveInfinity(inValue))
		return PositiveInfinity;
	else if (inValue == 0.0d)
		return Zero;
 
	if (inValue > Int64.MaxValue)
		throw new OverflowException(string.Format("Double {0} too large", inValue));
 
	if (inValue < -Int64.MaxValue)
		throw new OverflowException(string.Format("Double {0} too small", inValue));
 
	if (-EpsilonDouble < inValue && inValue < EpsilonDouble)
		throw new ArithmeticException(string.Format("Double {0} cannot be represented", inValue));
 
	int sign = Math.Sign(inValue);
	inValue = Math.Abs(inValue);
 
	return ConvertPositiveDouble(sign, inValue);
}
And this is the helper function that does the real work:
private static Fraction ConvertPositiveDouble(int sign, double inValue)
{
	// Shamelessly stolen from http://homepage.smc.edu/kennedy_john/CONFRAC.PDF
	// with AccuracyFactor == double.Episilon
	long fractionNumerator = (long)inValue;
	double fractionDenominator = 1;
	double previousDenominator = 0;
	double remainingDigits = inValue;
	int maxIterations = 594;	// found at http://www.ozgrid.com/forum/archive/index.php/t-22530.html

	while (remainingDigits != Math.Floor(remainingDigits) 
		&& Math.Abs(inValue - (fractionNumerator / fractionDenominator)) > double.Epsilon)
	{
		remainingDigits = 1.0 / (remainingDigits - Math.Floor(remainingDigits));
 
		double scratch = fractionDenominator;
 
		fractionDenominator =(Math.Floor(remainingDigits) * fractionDenominator) + previousDenominator;
		fractionNumerator = (long)(inValue * fractionDenominator + 0.5);
 
		previousDenominator = scratch;
 
		if (maxIterations-- < 0)
			break;
	}
 
	return new Fraction(fractionNumerator * sign, (long)fractionDenominator);
}
Lastly, you can reduce using this sort of code:
public static void ReduceFraction(ref Fraction frac)
{
	// clean up the NaNs and infinites
	if (frac.m_Denominator == 0)
	{
		frac.m_Numerator = (long)NormalizeIndeterminate(frac.m_Numerator);
		return;
	}
 
	// all forms of zero are alike.
	if (frac.m_Numerator == 0)
	{
		frac.m_Denominator = 1;
		return;
	}
	            
	long iGCD = GCD(frac.m_Numerator, frac.m_Denominator);
	frac.m_Numerator /= iGCD;
	frac.m_Denominator /= iGCD;
	        
	// if negative sign in denominator
	if ( frac.m_Denominator < 0 )
	{
		//move negative sign to numerator
		frac.m_Numerator = - frac.m_Numerator;
		frac.m_Denominator = - frac.m_Denominator;  
	}
}
Which needs the Greatest-common-divisor routine:
private static long GCD(long left, long right)
{
	// take absolute values
	if (left < 0)
		left = - left;
 
	if (right < 0)
		right = - right;
	            
	// if we're dealing with any zero or one, the GCD is 1
	if (left < 2 || right < 2)
		return 1;
 
	do
	{
		if (left < right)
		{
			long temp = left;  // swap the two operands
			left = right;
			right = temp;
		}
 
		left %= right;
	} while (left != 0);
 
	return right;
}

GeneralRe: Decimal and Floating-point to fractions is never easy. PinmemberGammill18:23 24 Jan '05  
GeneralRe: Decimal and Floating-point to fractions is never easy. PinmemberMarc Brooks18:54 24 Jan '05  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120528.1 | Last Updated 23 Jan 2005
Article Copyright 2005 by Gammill
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid