Decimal to Fraction

Share your advanced PureBasic knowledge/code with the community.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Decimal to Fraction

Post by Xombie »

Code updated For 5.20+

This is for inc in reply to a post of his but I thought I'd post it here since it's a small trick.

Code: Select all

Procedure.s ToFraction(Value.s)
  
  HoldString.s
  
  HoldPosition = FindString(Value, ".", 1)
  ;
  HoldString = Mid(Value, HoldPosition + 1, Len(Value) - HoldPosition)
  ;
  HoldLength = Len(HoldString)
  ;
  Decimal = Val(HoldString)
  ;
  Integer = Val(Mid(Value, 1, HoldPosition))
  ;
  Numerator = Decimal
  ;
  Denominator = Pow(10, HoldLength)
  ;
  If Numerator < Denominator
    ;
    GCD01 = Numerator
    GCD02 = Denominator
    ;
  Else
    ;
    GCD01 = Denominator
    GCD02 = Numerator
    ;
  EndIf
  ;
  Repeat
    ;
    GCD03 = GCD02 % GCD01
    ;
    If GCD03 = 0 : Break : EndIf
    ;
    GCD02 = GCD01
    GCD01 = GCD03
    ;
  ForEver
  ;
  Numerator / GCD01
  ;
  Denominator / GCD01
  ;
  HoldString = Str(Integer) + " " + Str(Numerator) + "/" + Str(Denominator)
  ;
  ProcedureReturn HoldString
  ;
EndProcedure
Debug ToFraction("1.5")
Debug ToFraction("5.414561")
Debug ToFraction("1.094017094")
As their results. This is not the fastest or most efficient but ... oh well :)

EDIT: Updated to show "Integer Num/Denom" so the whole integer is separate from the fraction.
Last edited by Xombie on Tue Jan 24, 2006 2:27 am, edited 1 time in total.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Is that the Euclidean algorithm you're using there? :)

That's a good method.
I may look like a mule, but I'm not a complete ass.
inc.
Enthusiast
Enthusiast
Posts: 406
Joined: Thu May 06, 2004 4:28 pm
Location: Cologne/GER

Post by inc. »

While searching in the www I've found a VB code I did port to PB :)

Code: Select all

Procedure.s Dec2Frac(f.f)
  
  df.f
  lUpperPart.l
  lLowerPart.l
  
  lUpperPart = 1
  lLowerPart = 1
  
  df = lUpperPart / lLowerPart
  While (df <> f )
    If (df < f)
      lUpperPart = lUpperPart + 1
    Else
      lLowerPart = lLowerPart + 1
      lUpperPart = f * lLowerPart
    EndIf
    df = lUpperPart / lLowerPart
  Wend
  ProcedureReturn Str(lUpperPart) + ":" + Str(lLowerPart)
EndProcedure
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

That's cool. But try our two functions together.

Code: Select all

Debug Dec2Frac(2.259822)
Debug ToFraction("2.259822")
Yields

Code: Select all

4314:1909
2 129911/500000
Using MS calc, yours comes out to "2.2598218962807752750130958617077" while mine comes out to "2.259822"

Also, my routine is roughly 23 times faster than the converted VB routine and I didn't even try any optimizations.
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

I've made this algorithm:

Code: Select all

Procedure.s FloatToFraction(n.f)
  decimales.f=n-Int(n)
  denominador.f=Pow(10,Len(StrF(decimales))-2)
  numerador.f=Int(n*denominador.f)
  d.l=100
  Repeat
    While numerador/d=Int(numerador/d) And denominador/d=Int(denominador/d)
      numerador/d:denominador/d
    Wend
    d.l-1
  Until d.l=1
  ProcedureReturn StrF(n.f)+" = "+Str(numerador.f)+" / "+Str(denominador.f)
EndProcedure

n.f=2.259822
Debug FloatToFraction(n.f)
I've tried to do it in assembler in order to allow 80 bit floats, but it's hard.
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

Post by Dr. Dri »

@Xombie
Your code is nice but you should receive a Float and then make a String with it...

Code: Select all

Debug StrF(2.259822, 17)
Dri ;)
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I'll leave that up to the user, Dr. Dri. Passing it as a string allows for large numbers and allows the user to create it anyway they like.

And yup, srod. It uses the Euclidean algorithim to find the GCD in order to reduce the fraction to it's simplest form. I looked it up on wikipedia :)

I have an ASM version that's slightly faster but it's clunky. I only did it to practice more asm stuffs.

Code: Select all

Procedure.s ToFractionASM(Value.s)
   ;
   HoldResult.s
   ; String used to return the result.
   HoldValue.l
   ; Used to temporarily store a value.
   !MOV esi, dword[esp]
   ; Store the start of the string.
   !DEC esi
   ;
   !.Start:
   ; The start of the loop used to locate the decimal character.
   !INC esi
   ; Increment to the next character.
   !MOVSX eax, byte[esi]
   ; Store the current character.
   !CMP eax, 0
   !JNE @f
   ; Test for the eol character. 
   ProcedureReturn Value
   ; This is not a fraction.  Exit the procedure.  "Value/1" = "Value"
   !@@:
   ; Character was not the eol character.
   !CMP eax, 46
   !JNE .NotDecimal
   ; Test for the decimal character.
   ;- Begin Conversion
   !CMP esi, dword[esp]
   !JE .BadValue
   ; There must be a numeric value before the decimal place.
   !MOV edx, esi
   ; Store the address of the decimal character.
   !MOV ebx, 0
   ; Used to store the numerator.
   !MOV eax, 1
   ; Used powers to convert the string to a numeric.
   !.LoopInteger:
   ; Loop used to convert numerator to value.
   !DEC esi
   ;
   !CMP esi, dword[esp]
   !JB .ConvertDecimal
   ; Before the first numeral - done converting the integer.
   !MOVSX edi, byte[esi]
   ; Store the ascii value in edi
   !SUB edi, 48
   ; Convert the ascii code to it's numeric value.
   !IMUL edi, eax
   ; Convert the number to it's place.
   !ADD ebx, edi
   ; Increase the size of the numerator.
   !IMUL eax, 10
   ; Increment ebp to the next power.
   !JMP .LoopInteger
   ; Process the next character.
   !.ConvertDecimal:
   ; Loop used to convert denominator to value.
   !MOV esi, edx
   ; Point to the decimal place.
   !@@:
   !INC esi
   !MOVSX edi, byte[esi]
   !MOV [esp + 8], edi
   !CMP edi, 0
   !JNE @b
   ; Loop until the eol character.
   !MOV edx, 0
   ; Used to store the denominator.
   !MOV eax, 1
   ;
   !.LoopDecimal:
   ; Begins the loop to convert the decimal to denominator.
   !DEC esi
   ; Move to the last character in the string.
   !MOVSX edi, byte[esi]
   ; Store the current numeral.
   !CMP edi, 46
   !JE .ConvertFinished
   ; Test for the decimal character.
   !SUB edi, 48
   ; Convert the ascii code to it's numeric value.
   !IMUL edi, eax
   ; Convert the number to it's place.
   !ADD edx, edi
   ; Increase the size of the denominator.
   !IMUL eax, 10
   ; Increment ebp to the next power.
   !JMP .LoopDecimal
   ; Process the next character.
   !.ConvertFinished:
   ;
   !MOV edi, ebx
   ; Store the original integer value.
   !MOV ebx, edx
   ;
   !MOV edx, eax
   ; Store the denominator.
   !MOV esi, ebx
   !MOV ebp, edx
   ; esi will contain the original numerator while ebp contains the original denominator.
   !MOV eax, ebx 
   !MOV ebx, edx
   ; Store the numerator and denomator 
   !CMP eax, ebx
   !JA @f
   ; Test if the numerator is greater than the denominator.
   !XCHG eax, ebx
   ; Swap the two if the denominator is larger than the numerator. 
   !@@:
   ; Start the loop to find the GCD. 
   !CDQ
   ;
   !IDIV ebx
   ; 
   !CMP edx, 0
   !JE @f
   ; When the remainder is zero, the GCD is located.
   !MOV eax, ebx
   !MOV ebx, edx
   ;
   !JMP @b
   ; GCD is not found.
   !@@:
   ; ebx now contains the GCD
   !MOV dword[esp + 8], edi
   HoldResult = Str(HoldValue) + " "
   ;
   !MOV eax, esi
   !CDQ
   !IDIV ebx
   !MOV dword[esp + 8], eax
   HoldResult = HoldResult + Str(HoldValue)+"/"
   !MOV eax, ebp
   !CDQ
   !IDIV ebx
   !MOV dword[esp + 8], eax
   HoldResult = HoldResult + Str(HoldValue)
   ;
   !JMP .Finished
   ;
   ;- End Conversion
   !.NotDecimal:
   ; Character was not the decimal character.
   !CMP eax, 48
   !JB .BadValue
   !CMP eax, 57
   !JA .BadValue
   ; Test to ensure the character is a numeric.
   !JMP .Start
   ; Process the next character.
   !.Finished:
   ;
   ProcedureReturn HoldResult
   ;
   !.BadValue:
   ; There is a problem with the value string (most likely non-numeric).
   ProcedureReturn Value
   ; Return the value string.
EndProcedure
It's nothing great and I'm sure Psychophanta will be secretly laughing at me behind his computer :D
User avatar
Michael Vogel
Addict
Addict
Posts: 2797
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Post by Michael Vogel »

I have an ASM version that's slightly faster but it's clunky.
Nice code, but maybe there are some small issues inside (at least when I using StrD):

Code: Select all

Debug ToFractionASM(StrD(7.654))     ; something like 7 -nnn/mmm
Debug ToFractionASM("7.654")         ; ok,  7 327/500
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

By the way of this thread; beware to this bug:
http://www.purebasic.fr/english/viewtopic.php?t=20540
It is related to possible malfunctions in the results for an algorithm for topics like this.
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
Post Reply