Convert Number to Fraction

I haven’t seen any other techniques for converting real number’s into fractions on this forum, so I just thought I’d share this version.
Along with ‘fractionising’ normal rational numbers, I have added a small example to illustrated how multiples of pi and any other known irrational numbers can also be found.

This has not been properly tested, so some bugs might be present.
I look forward to other versions that people have or any improvements on mine.

property tolerance : 10 -- Number of repeats
property minimumRemainder : 1.0E-7

property piSymbol : «data utxt03C0» as Unicode text
property squareRootSymbol : «data utxt221A» as Unicode text

-- Set up list of units
set unitList to {{unit:1, symbol:null}, {unit:pi, symbol:piSymbol}, {unit:(2 ^ 0.5), symbol:(squareRootSymbol & 2)}}

fractionise(2 ^ 0.5 * 0.45, unitList, tolerance, minimumRemainder)
--> "9√2/20"

fractionise(3 * pi / 4, unitList, tolerance, minimumRemainder)
--> "3Ï€/4"

fractionise(12347 / 14512, unitList, 15, 1.0E-5) -- Custom tolerance levels
--> "12347/14512"


on fractionise(theNumber, unitList, tolerance, minimumRemainder)
	repeat with thisUnit in unitList
		tell fractionParts(theNumber / (thisUnit's unit), tolerance, minimumRemainder) to if it is not false then ¬
			return my formatFraction(it, thisUnit's symbol)
	end repeat
	return false
end fractionise

on fractionParts(n, tolerance, minimumRemainder)
	if n mod 1 is 0 then return {numerator:n div 1, denominator:1}
	
	set i to n
	set b to 1
	repeat tolerance times
		if i < 1 then
			set remainder to i
		else
			set remainder to i - i div 1 -- Does the same as 'i mod 1', but more accurate
		end if
		if remainder < minimumRemainder then return {numerator:(n * b div 1), denominator:b div 1}
		set i to 1 / remainder
		set b to b * i
	end repeat
	return false
end fractionParts

on formatFraction(fraction, unitSymbol)
	tell fraction
		if unitSymbol is null then
			set fracString to its numerator as Unicode text
		else if its numerator is 1 then
			set fracString to unitSymbol
		else
			set fracString to (its numerator as Unicode text) & unitSymbol
		end if
		if its denominator is not 1 then set fracString to fracString & "/" & its denominator
	end tell
	
	return fracString
end formatFraction

Very nice, QD. Produces some interesting results occasionally:

fractionise((0.1111, unitList, tolerance, 1.0E-5) → 1111/10000

fractionise(0.11111, unitList, tolerance, 1.0E-5) → false

fractionise(0.111111, unitList, tolerance, 1.0E-5) → 1/9

Thanks Adam,

It appears mod is slightly lossy, probably due to minor floating point errors.
Here is a comparison with a custom handler:

on modN(n, d)
    return n - n div d
end modN

set n to 1 / 0.1111
return {correctResult:1 / 1111, ASMod:n mod 1, customMod:modN(n, 1)}
--> {correctResult:9.000900090009E-4, ASMod:9.00090008997267E-4, customMod:9.0009000900082E-4}

As can be seen, the custom handler is more accurate, but not perfect.

I’ve now changed the line (in my original post):

set remainder to i mod 1

…into the following, which should hopefully improve accuracy:

if i < 1 then set remainder to i else set remainder to i - i div 1 end if
This now gives:

fractionise(0.1111, unitList, tolerance, 1.0E-10)
--> "1111/10000"

fractionise(0.11111, unitList, tolerance, 1.0E-7)
--> "11111/100000"

fractionise(0.111111, unitList, tolerance, 1.0E-5)
--> "1/9"

I’m really impressed that it gets this right: fractionise(4.188790204786, unitList, tolerance, minimumRemainder) as 4Ï€/3

A very pleasing script, Qwerty. I attempted something similar a few years ago, but concluded that it couldn’t sensibly be done. This not only does it with a fair degree of success but returns niceties like pi and the square root of two. Nice one. :cool: