Here's one last take on the "closest fraction with power-of-2 denominator" concept.
This one uses a SysRPL implementation, and instead of requiring the maximum denominator as a given argument, that value is obtained from a global variable named "MxDen". If that variable isn't found in the current path, a new one containing a default value of 64 is created for subsequent use. As such, there is only one argument required for this program: a value to be converted (either numeric or symbolic). The final result depends on which type is given as well as the value of MxDen.
When I was playing around with this, I realized that I often wanted to go back-and-forth between the symbolic and real/approximate representations of some value. So this version evolved to function as a "toggler" between those two forms. If given a real/approximate number, it is converted to the nearest
rational form. If given a symbolic, it is converted to the nearest
real form. In both cases, the result is based on the currently specified maximum denominator. This means that if you give the program an argument of π with a max denominator of 64, the result will be a decimal representation of the nearest "power of 2" rational that approximates π in that range (3+9/64): 3.140625. An exact integer is treated the same as a symbolic value; the result will be that same integer in approximate form.
The source code below is appropriate for the 50g's on-board MASD compiler (
ASM from menu 256), and assumes you have
the HP extable installed:
Code:
!NO CODE
!ASM
DC Z0_ 273AB
!RPL
DEFINE MaxDenominator ID MxDen
::
CK1NOLASTWD
( validate/create max denominator value )
' MaxDenominator @
ITE ::
( MaxDenominator found - check validity )
ERRSET ::
CKREAL
%2 %< IT ERRJMP
;
ERRTRAP ::
"Bad MxDen Value" EXITMSGSTO
#EXITERR ERRORSTO
ERRJMP
;
;
::
( MaxDenominator not found - store default value )
% 64 ' MaxDenominator STO
;
( main conversion routine - placed on return stack for later processing )
' ::
( setup locals )
DUP %0< SWAP ( neg )
%ABS %>%% ( target )
MaxDenominator CKREAL %>%% ( max )
%%2 ( current )
3PICK ( numerator )
%%1 ( denominator )
DUP ( delta )
{{ delta denominator numerator current max target neg }}
( main loop - check all denominators up to max for closest fit )
BEGIN
current max %%<=
WHILE ::
( determine current numerator )
current target %%* ( multiply target number by current denominator )
%%>% %0 RNDXY %>%% ( round the result to the nearest integer )
( determine current delta )
DUP current %%/ ( divide test numerator by current denominator )
target %%- %%ABS ( delta = difference between current fraction and target )
( if stored delta is larger, update stored values )
delta OVER %%>
ITE ::
!delta !numerator
current !denominator
;
2DROP
( increment current denominator to next power of 2 )
current %%2 %%* !current
;
REPEAT
( determine c,b,a for "a+b/c" )
numerator denominator 2%%>%
2DUP %MOD
ROT OVER %- 3PICK %/
( convert to exact integers & reorder to a,b,c )
FPTR2 ^R>Z UNROT
FPTR2 ^R>Z SWAP
FPTR2 ^R>Z
( convert a,b,c to formatted symbolic )
3PICK Z0_ Z= ( is a=0? )
3PICK Z0_ Z= ( is b=0? )
::
( a & b are 0: return 0 )
2DUP ANDcase 4DROP
( only a is 0: return b/c )
2DUP NOTAND case ::
2DROP ROTDROP
' x/
BINT3 SYMBN
;
( only b is 0: return a )
SWAP NOTAND case 2DROP
( otherwise return a+b/c )
' x/ ' x+
BINT5 SYMBN
;
( negate if appropriate )
neg IT ::
DUPTYPESYMB? ITE ::
INNERCOMP
' xNEG SWAP
#1+ SYMBN
;
FPTR2 ^QNeg
;
( release locals )
ABND
;
>R
CK&DISPATCH1
( real/approximate number: main routine will handle )
real
NOP
( exact integer: convert to real and leave on stack without further processing )
BINT255d ::
FPTR2 ^Z>R
RDROP
;
( symbolic: ->NUM, process main routine, then ->NUM again for rounded result )
symb ::
CRUNCH
R> EVAL
CRUNCH
;
;
@
After compiling, store the code into a named variable (I call mine "Imp" for "Imperial") and you're all set! A zipped pre-compiled version is also attached.