Page 1 of 1

Decimal to Fraction

Posted: Mon Jan 23, 2006 10:53 pm
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.

Posted: Mon Jan 23, 2006 11:34 pm
by srod
Is that the Euclidean algorithm you're using there? :)

That's a good method.

Posted: Tue Jan 24, 2006 1:42 am
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

Posted: Tue Jan 24, 2006 2:28 am
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.

Posted: Tue Jan 24, 2006 10:13 am
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.

Posted: Tue Jan 24, 2006 3:37 pm
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 ;)

Posted: Tue Jan 24, 2006 6:00 pm
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

Posted: Tue Mar 21, 2006 10:31 am
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

Posted: Tue Mar 21, 2006 11:03 am
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.