{********** Pascal/Z compiler options **********} {$C-}{ control-c keypress checking OFF } {$M-}{ integer mult & divd error checking OFF } {********** Pascal/Z compiler options **********} PROGRAM SuperMetrics; { PROGRAM TITLE: An Automatic Metric Conversion Program } { WRITTEN BY: David A. Mundie } { } { 12/15/82 - modified for Pascal/Z by Raymond E. Penley } { } { SUMMARY: The program distinguishes rigorously between customary units, primary metric units, and secondary metric units. By "primary" metric units is meant the System International (SI) base units such as metre, kilogram, kelvin, and so on, as well as the derived units such as watt, newton, m/s, pascal, and volt. By "secondary" metric units is meant units like degree C and km/h which are accepted but not part of SI, along with the formulas for derived units with special names, eg. kg m/s as the formulas for the newton. The program automatically converts customary and secondary units to primary units. Primary units may be converted to customary by using the "c" command, while the "s" command converts them to secondary units. PREFIXES USED BY SUPERMETRIC FOR MEASURMENTS OTHER THAN VOLUMES & AREAS. power prefix abbreviation ----- ------ ------------ 10^18 exa E 10^15 peta P 10^12 tera T 10^9 giga G 10^6 mega M 10^3 kilo k 10^-3 milli m 10^-6 micro u 10^-9 nano n 10^-12 pico p 10^-15 femto f 10^-18 atto a PREFIXES USED BY SUPERMETRIC FOR VOLUMES AND AREAS power prefix abbreviation ----- ------ ------------ 10^3 kilo k 10^2 hecto h 10^1 deka da 10^-1 deci d 10^-2 centi c 10^-3 milli m DIFFERENCES BETWEEN SUPERMETRIC AND CORRECT System International (SI) SYMBOLS. correct SI form SUPERMETRIC mu u da D . * (multiplication) o $ (degrees) m2, etc. m2 } CONST CmdLine = 'COMMANDS: c(ustomary, h(elp, s(econdary, l(ist, f(inished'; normp = 'afpnum kMGTPE'; { normal prefixes } specp = 'mcd Dhk'; { special prefixes for areas and volumes } maxentries = 100; z = 48; { ord('0') } TYPE index = 0..maxentries; strng = STRING 40; entry = RECORD left,right: strng; factor : real END; string0 = STRING 0; string255 = STRING 255; VAR bell : char; { console bell } curtop : index; { current top of table } current : index; { points to current entry } finished : boolean; leftside : boolean; line : strng; { one line of user input } m : real; { the measurement } maxcust : index; { top of customary section of table } oldm,oldf : real; p : integer; { the precision } table : ARRAY [ index ] OF entry; top : index; { permanent top of table } u : strng; { the unit } {*********************** Utilities ******************************************} FUNCTION length ( source: string255 ): integer; external; FUNCTION pos ( PATTERN, SOURCE: string255 ): integer; external; PROCEDURE delete ( var source: string0; start, count: integer ); external; PROCEDURE copy ( var sub: string0; source: string255; here, count: integer ); external; PROCEDURE insert ( pattern: string255; var dest: string0; posn: integer); external; PROCEDURE concat ( var new: string0; arg1,arg2: string255 ); external; {*********************** mathematical utilities *****************************} FUNCTION floor ( r: real ): integer; BEGIN floor := trunc( r - ord( (r<0.0) AND (r<>trunc(r)) ) ) END; FUNCTION nl ( a: real ): real; BEGIN IF a < 1.0 THEN nl := -LN( a ) ELSE nl := LN( a ) END; FUNCTION power ( i,j: integer ): real; BEGIN power := exp ( nl(abs(i)) * j) END; FUNCTION log ( r: real ): real; BEGIN log := nl(abs(r)) / LN(10.0) END; FUNCTION norm ( r: real ): real; BEGIN norm := r / power(10,floor(log(r))) END; {************ convert a string to a real number *****************************} FUNCTION value ( VAR s: strng; VAR p: integer ): real; { returns p = number of significant digits } CONST limit = 1.67772E6; { (2**23)/5) } VAR a,y : real; e,i,j,p2 : integer; neg, negexp, gtl : boolean; digits: SET OF char; FUNCTION val ( a : real; ch: char ): real; BEGIN val := 10.0 * a + ord(ch)-z END{val}; BEGIN i := 1; p := 0; p2 := 0; gtl := false; digits := ['0'..'9']; append(s,'%'); { safety character } a := 0.0; e := 0; neg := (s[i]='-'); WHILE s[i]=' ' DO i := i + 1; IF (s[i]='+') OR (neg) THEN i := i + 1; WHILE s[i] IN digits DO BEGIN IF s[i]='0' THEN p2 := p2 + 1 ELSE BEGIN p := p+p2+1; p2 := 0; gtl := true END; IF a0 THEN value := y*power(10,e) ELSE value := y; WHILE s[i]=' ' DO i := i+1; copy ( s,s,i,length(s)-i) END{value}; {************* Write a real in appropriate format and return a blank *********} FUNCTION f ( r:real ): char; CONST width = 23; VAR intpart,decimals,floating: integer; BEGIN intpart := floor(log(r)); decimals := p - intpart - 1; IF (r>10000.0) OR (r<0.0001) THEN {floating point} write ( r:width ) ELSE IF decimals <= 0 THEN {integer} write ( round(r): width ) ELSE {fixed point} write ( r:width:decimals ); f := ' ' END; {*************** Special handling for temperatures ***************************} PROCEDURE temperature ( VAR m: real; b: boolean; fact: integer ); VAR d: integer; BEGIN d := p - floor(log(m))-1; m := m + fact * 273.15 + fact * 186.52 * ord( b ); p := d + floor(log(m)) + 1 END; {*************** Find u in the table of units *******************************} FUNCTION inlist: boolean; VAR t: strng; FUNCTION match ( s: strng ): boolean; BEGIN match := ((u=s) OR (t=s)) END; BEGIN { start scan with left list } leftside := true; current := 1; t := u; IF length(t) > 1 THEN delete(t,1,1); WHILE (NOT(match(table[current].left))) and (current<=curtop) DO current := current + 1; IF current<=curtop THEN inlist := true ELSE BEGIN { scan the right list starting at the top working towards the bottom } current := curtop; leftside := false; WHILE (NOT(match(table[current].right))) and (current>0) DO current := current - 1; inlist := (current>0) END END{inlist}; {*************** Add correct metric prefix **********************************} PROCEDURE prefix ( m: real; u: strng ); PROCEDURE pref ( a: strng; fac,term: integer ); VAR i,range: integer; BEGIN range := floor ( log(m) / fac ); IF abs(range) > term THEN BEGIN {*** range := term * ( 1 - (2 * ord( (range<(-term)) )) ); ***} range := term * ( 1 - (2 * ord( (range<=term) )) ); END; m := m / power ( 10,(fac*range) ); IF range<>0 THEN BEGIN a := a[range+term+1]; concat ( u,a,u );{ u := concat(a,u); } writeln(f(m),u ) END END{pref}; BEGIN{prefix} IF pos('2',u)=2 THEN pref(specp,2,3) ELSE IF pos('3',u)=2 THEN pref(specp,3,3) ELSE pref(normp,3,6) END{prefix}; {******************** Convert to primary units *******************************} PROCEDURE primary; VAR oldp: integer; BEGIN WITH table[current] DO BEGIN IF u='mpg' THEN m := 1.0 / m; IF length(u)=2 THEN IF (u[1]='$') AND (u[2] IN ['F','C']) THEN temperature(m,(u[2]='F'),1); oldm := m; oldf := factor; oldp := p; p := p + ord( norm(m) * norm(factor) >= 10.0 ); u := right; m := m * factor; writeln ( f(m),u ); prefix(m,u); p := oldp; leftside := false END END{primary}; {*************** check metric prefix and adjust if necessary *****************} PROCEDURE normalize ( VAR m: real; VAR u: strng ); VAR s: strng; PROCEDURE depref ( a: strng; fac,term: integer ); VAR range,k : integer; needspref: boolean; BEGIN needspref := ( floor( log(m)/fac )<>0 ); IF pos(s,u)=2 THEN BEGIN range := term+1; FOR k:=1 TO length(a) DO BEGIN IF u[1]=a[k] THEN range := k-term-1 END; k := range+term+1; IF (k>=1) AND (k<=(term*2+1)) THEN BEGIN m := m * power ( 10,fac*range ); delete(u,1,1); writeln( f(m),u ) END ELSE writeln('illegal prefix ignored') END; IF needspref THEN prefix(m,u) END{depref}; BEGIN{normalize} WITH table[current] DO BEGIN IF leftside THEN s := left ELSE s := right END; IF pos('2',s) = 2 THEN depref(specp,2,3) ELSE IF pos('3',s)=2 THEN depref(specp,3,3) ELSE depref(normp,3,6) END{normalize}; {*************** Convert to customary or secondary units *********************} PROCEDURE custandsec ( m: real ); VAR oldp: integer; BEGIN WITH table[current] DO BEGIN oldp := p; p := p + ord( norm(oldm) * norm(oldf/factor) >= 10.0 ); m := m / factor; IF (u='m3/m') AND (current<=maxcust) THEN m := 1.0 / m; IF u='K' THEN temperature ( m, (left[2]='F'), -1 ); writeln ( f(m), left ); IF current > maxcust THEN prefix ( m, left ); p := oldp END END{custandsec}; {********** Pascal/Z compiler options **********} {$F-}{ floating point error checking OFF } {$R-}{ range checking OFF } {********** Pascal/Z compiler options **********} {*********************** Set up the table ***********************************} PROCEDURE initialize; PROCEDURE data ( L,R: strng; f: real ); BEGIN curtop := curtop+1; WITH table[curtop] DO BEGIN left := L; right := R; factor := f; END END; BEGIN{initialize} bell := chr(7); WITH table[0] DO BEGIN left := 'bottom'; right := 'bottom'; factor := 0.0 END; curtop := 0; { CUSTOMARY UNITS/PRIMARY UNITS/CONVERSION FACTOR } data ( '$F', 'K', 5.5556e-1 ); data ( 'mpg', 'm3/m', 2.352e-6 ); data ( 'horsepower', 'W', 7.355e2 ); data ( 'inch of mercury', 'Pa', 3.37685e3 ); data ( 'mph', 'm/s', 4.4704e-1 ); data ( 'yard', 'm', 9.144e-1 ); data ( 'yard2', 'm2', 8.361274e-1 ); data ( 'acre', 'm2', 4047.0 ); data ( 'barrel', 'm3', 0.159 ); data ( 'kCal', 'J', 4.1868e3 ); data ( 'BTU', 'J', 1055.0 ); data ( 'Curie', 'Bq', 3.7e10 ); maxcust := curtop; { SECONDARY UNITS/PRIMARY UNITS/CONVERSION FACTOR } data ( 'L', 'm3', 1.0e-3 ); data ( 'N/m2', 'Pa', 1.0 ); data ('L/100 km', 'm3/m', 1.0e-8 ); data ( 'm/h', 'm/s', 2.777e-4 ); data ('kW-h', 'J', 3.6e6 ); data ('$C', 'K', 1.0 ); data ('N*m', 'J', 1.0 ); data ( 'top','top', 0.0 ); { strings left & right must be initted } top := curtop END{initialize}; {*************** Main subprograms *******************************************} PROCEDURE give_help; { WRITTEN BY: Raymond E. Penley } { DATE WRITTEN: Dec 15, 1982 } BEGIN writeln; writeln( ' The program distinguishes rigorously between customary units,'); writeln( 'primary metric units, and secondary metric units. By "primary" metric'); writeln( 'units is meant the System International (SI) base units such as metre,'); writeln( 'kilogram, kelvin, and so on, as well as the derived units such as'); writeln( 'watt, newton, m/s, pascal, and volt. By "secondary" metric units is'); writeln( 'meant units like degree C and km/h which are accepted but not part of'); writeln( 'SI, along with the formulas for derived units with special names, eg.'); writeln( 'kg m/s as the formulas for the newton.'); writeln; writeln( ' Primary units may be converted to customary by using the'); writeln( '"c" command, while the "s" command converts them to secondary units.'); writeln; writeln('Enter commands like:'); writeln('Measure and unit>>5700 kJ'); writeln(' 5.70000E+06 J'); writeln(' 5.7 MJ'); writeln; writeln('Measure and unit>>secondary'); writeln(' 5.70000E+06 N*m'); writeln(' 5.7 MN*m'); writeln; END{give_help}; PROCEDURE commands; VAR i: integer; BEGIN CASE line[1] OF 'F','f': finished := true; 'S','s': IF (inlist) AND (current>maxcust) AND (NOT leftside) THEN custandsec(m); 'H','h': give_help; 'C','c': BEGIN curtop := maxcust; IF inlist THEN custandsec ( m ); curtop := top END; 'L','l': BEGIN writeln( 'CUSTOMARY UNITS PRIMARY UNITS CONVERSION FACTOR' ); FOR i:=1 TO maxcust DO BEGIN WITH table[i] DO writeln ( left:15, ' ',right:15,' ',factor:15) END; writeln; writeln( 'SECONDARY UNITS PRIMARY UNITS CONVERSION FACTOR' ); FOR i:=maxcust+1 TO top-1 DO BEGIN WITH table[i] DO writeln ( left:15, ' ',right:15,' ',factor:15) END; END ELSE: BEGIN writeln; writeln( CmdLine ) END END{case}; writeln END{commands}; PROCEDURE process; BEGIN m := value ( line,p ); u := line; oldf := 1.0; IF NOT inlist THEN writeln(bell, 'unit not available') ELSE BEGIN IF (current > maxcust) OR (NOT leftside) THEN normalize ( m,u ); IF leftside THEN primary END; writeln END{process}; BEGIN { SuperMetrics } finished := false; initialize; writeln(' ':22, 'SUPERMETRIC CONVERSION PROGRAM'); writeln; writeln; writeln; writeln; writeln ( CmdLine ); REPEAT writeln; write('Measure and unit >>'); readln(line); IF (line[1] IN ['0'..'9','+','-']) THEN process ELSE commands UNTIL finished END.