Use PowerShell to Convert a Decimal to a Fraction

While working on a project, I had the need to convert a decimal to a fraction and while I know that I could do some math outside of PowerShell (or look it up online), I wanted to be able to do this in PowerShell. Unfortunately, I couldn’t find anything in the PowerShell world that did this. That being the case, I decided to take this on and see what I could do.

This was a nice challenge coming up with a way to take a decimal and then present it as a fraction (note that the fraction is just going to be a string). In the end, I went with iterating through numbers for both the numerator and the denominator until I either get close to a usable fraction or actually find the fraction itself. This may not be the most efficient way of accomplishing the task, but against smaller decimals it works just fine.

My parameters are:

  • Decimal
    • The value that we will  converting to a fraction
  • ClosesDenominator
    • Used to get the most precision for the fraction by getting as close to the accurate value as possible
  • ShowMixedFraction
    • Displays the value as a Mixed Fraction instead of an Improper Fraction
  • AsObject
    • Displays the value as an object instead of as a fraction
Param(      
    [parameter(ValueFromPipeline=$True,ParameterSetName='Object')] 
    [parameter(ValueFromPipeline=$True,Position=0,ParameterSetName='NonObject')] 
    [double]$Decimal = .5,
    [parameter(ParameterSetName='Object')] 
    [parameter(ParameterSetName='NonObject')] 
    [int]$ClosestDenominator = 100,
    [parameter(ParameterSetName='NonObject')]  
    [switch]$ShowMixedFraction,
    [parameter(ParameterSetName='Object')] 
    [switch]$AsObject
)

Up next is the piece of code that I use to set up to begin looking for a fraction:

$Break = $False
$Difference = 1
If ($Decimal -match '^(?<WholeNumber>\d*)?(?<DecimalValue>\.\d*)?$') {
    $WholeNumber = [int]$Matches.WholeNumber
    $DecimalValue = [double]$Matches.DecimalValue
}
$MaxDenominatorLength = ([string]$ClosestDenominator).Length
$DecimalLength = ([string]$Decimal).Split('.')[1].Length+1
$LengthDiff = $MaxDenominatorLength - $DecimalLength
If ($LengthDiff -lt 0) {
    Write-Warning @"
Decimal <$($Decimal)> is greater of length than expected Denomimator <$($ClosestDenominator)>.
Increase the size of ClosestDenomimator to <$(([string]$ClosestDenominator).PadRight($DecimalLength,'0'))> to produce more accurate results.
"@
} 

Here I am making sure that I can break when I need to (but just not yet) by setting $Break to $False. $Difference is used to help determine how close I am getting to finding that accurate fraction. In the case that I have a whole number with a decimal, I need to split those off to handle each one on its own. Lastly, I throw a friendly warning if the decimal length is larger than my ClosestDenominator length as this will affect the accuracy of the fractions. This is a little unique because if you want to deal with a repeating decimal, such as .33333…, then you know that it will be 1/3 but unless the ClosestDecimal is shorter than the decimal, then you will the accurate result of 3333/10000 in this case instead of 1/3. Just something to keep in mind and I will demo this later on.

Now onto the main part of the code that starts the recursive searching for the fraction:

If ($DecimalValue -ne 0) {
    #Denonimator - Needs to be 2 starting out
    For ($Denominator = 2; $Denominator -le $ClosestDenominator; $Denominator++) {
        #Numerator - Needs to be 1 starting out
        For ($Numerator = 1; $Numerator -lt $Denominator; $Numerator++) {
            Write-Verbose "Numerator:$($Numerator) Denominator:$($Denominator)"
            #Try to get as close to 0 as we can get
            $temp = [math]::Abs(($DecimalValue - ($Numerator / $Denominator)))
            Write-Verbose "Temp: $($Temp) / Difference: $($Difference)"
            If ($temp -lt $Difference) {                                                               
                Write-Verbose "Fraction: $($Numerator) / $($Denominator)"
                $Difference = $temp                         
                $Object = [pscustomobject]@{
                    WholeNumber = $WholeNumber
                    Numerator = $Numerator
                    Denominator = $Denominator
                }
                If ($Difference -eq 0) {
                    $Break = $True
                }                
            }
            If ($Break) {BREAK}
        }
        If ($Break) {BREAK}
    }
} Else {
    $Object = [pscustomobject]@{
        WholeNumber = $WholeNumber
        Numerator = 0
        Denominator = 1
    }    
}
If ($Object) {
    If ($PSBoundParameters.ContainsKey('AsObject')) {
        $Object
    } Else {
        If ($Object.WholeNumber -gt 0) {
            If ($PSBoundParameters.ContainsKey('ShowMixedFraction')) {
                "{0} {1}/{2}" -f $Object.WholeNumber, $Object.Numerator, $Object.Denominator               
            } Else {
                $Numerator = ($Object.Denominator * $Object.WholeNumber)+$Object.Numerator
                "{0}/{1}" -f $Numerator,$Object.Denominator                 
            }
        } Else {
            "{0}/{1}" -f $Object.Numerator, $Object.Denominator
        }
    }
}

A lot of things happening here, but nothing too crazy going on.  I start out by setting my starting Denominator to 2 (doing a 1 would treat anything greater than 0 as a whole number) and begin using that in a For() loop. This will continue to increment until I hit the ClosestDenominator that is defined by the parameter. Next, the numerator will begin working to increment by starting at 1 and will increment to the size of the Denominator before resetting for the next Denominator.

Now I take the decimal that I am trying to convert to a fraction and subtract it by the value of the division of the numerator and denominator to get as close or equal to zero. If I can get to zero, then I have the most accurate fraction, otherwise I continue going and go with the last closes value. An example is here showing how we get 3/4 from .75:

image

From there it is just a matter of taking the object that is created and formatting it to the type of fraction that we are expecting.

With all of that said, it is time for some examples of this in action.

Convert-DecimalToFraction –Decimal .5

image

Convert-DecimalToFraction –Decimal .33

image

Convert-DecimalToFraction –Decimal .333

image

Convert-DecimalToFraction –Decimal 1.15

image

Convert-DecimalToFraction –Decimal 1.15 –ShowMixedFraction

image

Convert-DecimalToFraction –Decimal 1.15 –AsObject

image

Convert-DecimalToFraction –Decimal .005 –ClosestDenominator 1000

image

Give the function a download from the link below and let me know what you think!

Download Convert-DecimalToFraction

https://gallery.technet.microsoft.com/scriptcenter/Convert-a-Decimal-to-a-7dc416be

About Boe Prox

Microsoft Cloud and Datacenter MVP working as a SQL DBA.
This entry was posted in powershell and tagged , , , . Bookmark the permalink.

2 Responses to Use PowerShell to Convert a Decimal to a Fraction

  1. Larry Weiss says:

    I find the -AsObject an interesting switch. In many cases I need the options of producing human-readable report output and the option of producing machine-oriented output. I scanned some other source libraries for any prior-art of a -AsObject switch but did not discover any. Is this your first time to use -AsObject ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s