home *** CD-ROM | disk | FTP | other *** search
/ Oakland CPM Archive / oakcpm.iso / sigm / vol133 / metrics.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1984-04-29  |  14.2 KB  |  619 lines

  1. {********** Pascal/Z compiler options **********}
  2. {$C-}{ control-c keypress checking OFF        }
  3. {$M-}{ integer mult & divd error checking OFF    }
  4. {********** Pascal/Z compiler options **********}
  5.  
  6.  
  7.  
  8. PROGRAM SuperMetrics;
  9. { PROGRAM TITLE: An Automatic Metric Conversion Program        }
  10. { WRITTEN BY:     David A. Mundie                }
  11. {                                }
  12. { 12/15/82 - modified for Pascal/Z by Raymond E. Penley        }
  13. {                                }
  14. { SUMMARY:
  15.     The program distinguishes rigorously between customary units,
  16.  primary metric units, and secondary metric units. By "primary" metric
  17.  units is meant the System International (SI) base units such as metre,
  18.  kilogram, kelvin, and so on, as well as the derived units such as
  19.  watt, newton, m/s, pascal, and volt.  By "secondary" metric units is
  20.  meant units like degree C and km/h which are accepted but not part of
  21.  SI, along with the formulas for derived units with special names, eg.
  22.  kg m/s as the formulas for the newton.
  23.  
  24.     The program automatically converts customary and secondary units
  25.  to primary units.  Primary units may be converted to customary by using
  26.  the "c" command, while the "s" command converts them to secondary units.
  27.  
  28.  PREFIXES USED BY SUPERMETRIC FOR MEASURMENTS OTHER THAN VOLUMES & AREAS.
  29.  
  30.         power   prefix  abbreviation
  31.     -----    ------    ------------
  32.     10^18    exa     E
  33.     10^15    peta     P
  34.     10^12    tera     T
  35.     10^9    giga     G
  36.     10^6    mega     M
  37.     10^3    kilo     k
  38.     10^-3    milli     m
  39.     10^-6    micro     u
  40.     10^-9    nano     n
  41.     10^-12    pico     p
  42.     10^-15    femto     f
  43.     10^-18    atto     a
  44.  
  45.  
  46.  PREFIXES USED BY SUPERMETRIC FOR VOLUMES AND AREAS
  47.  
  48.         power   prefix  abbreviation
  49.     -----    ------    ------------
  50.     10^3    kilo     k
  51.     10^2    hecto     h
  52.     10^1    deka     da
  53.     10^-1    deci     d
  54.     10^-2    centi     c
  55.     10^-3    milli     m
  56.  
  57.  DIFFERENCES BETWEEN SUPERMETRIC AND CORRECT System International (SI) SYMBOLS.
  58.  
  59.     correct SI form        SUPERMETRIC
  60.     mu            u
  61.     da            D
  62.     .            * (multiplication)
  63.     o            $ (degrees)
  64.     m2, etc.        m2
  65.  
  66.  
  67. }
  68. CONST 
  69.   CmdLine = 'COMMANDS: c(ustomary, h(elp, s(econdary, l(ist, f(inished';
  70.   normp   = 'afpnum kMGTPE';    { normal prefixes }
  71.   specp   = 'mcd Dhk';        { special prefixes for areas and volumes }
  72.   maxentries = 100;
  73.   z      = 48;            { ord('0')     }
  74.  
  75. TYPE 
  76.   index        = 0..maxentries;
  77.   strng        = STRING 40;
  78.   entry        = RECORD
  79.                 left,right: strng;
  80.                 factor : real
  81.               END;
  82.  
  83.   string0    = STRING 0;
  84.   string255    = STRING 255;
  85.  
  86. VAR 
  87.   bell        : char;        { console bell }
  88.   curtop    : index;    { current top of table }
  89.   current    : index;    { points to current entry }
  90.   finished    : boolean;
  91.   leftside    : boolean;
  92.   line        : strng;    { one line of user input }
  93.   m        : real;        { the measurement }
  94.   maxcust    : index;    { top of customary section of table }
  95.   oldm,oldf    : real;
  96.   p        : integer;    { the precision }
  97.   table        : ARRAY [ index ] OF entry;
  98.   top        : index;    { permanent top of table }
  99.   u        : strng;    { the unit }
  100.  
  101. {*********************** Utilities ******************************************}
  102.  
  103. FUNCTION length ( source: string255 ): integer;
  104.     external;
  105.  
  106. FUNCTION pos ( PATTERN, SOURCE: string255 ): integer;
  107.     external;
  108.  
  109. PROCEDURE delete ( var source: string0; start, count: integer );
  110.     external;
  111.  
  112. PROCEDURE copy ( var sub: string0; source: string255; here, count: integer );
  113.     external;
  114.  
  115. PROCEDURE insert ( pattern: string255; var dest: string0; posn: integer);
  116.     external;
  117.  
  118. PROCEDURE concat ( var new: string0; arg1,arg2: string255 );
  119.     external;
  120.  
  121. {*********************** mathematical utilities *****************************}
  122.  
  123. FUNCTION floor ( r: real ): integer;
  124. BEGIN
  125.   floor := trunc( r - ord( (r<0.0) AND (r<>trunc(r)) ) )
  126. END;
  127.  
  128. FUNCTION nl ( a: real ): real;
  129. BEGIN
  130.   IF a < 1.0
  131.      THEN nl := -LN( a )
  132.      ELSE nl := LN( a )
  133. END;
  134.  
  135. FUNCTION power ( i,j: integer ): real;
  136. BEGIN
  137.   power := exp ( nl(abs(i)) * j)
  138. END;
  139.  
  140. FUNCTION log ( r: real ): real;
  141. BEGIN
  142.   log := nl(abs(r)) / LN(10.0)
  143. END;
  144.  
  145. FUNCTION norm ( r: real ): real;
  146. BEGIN
  147.   norm := r / power(10,floor(log(r)))
  148. END;
  149.  
  150.  
  151. {************ convert a string to a real number *****************************}
  152.  
  153. FUNCTION value ( VAR s: strng; VAR p: integer ): real;
  154. { returns p = number of significant digits }
  155. CONST 
  156.   limit = 1.67772E6; { (2**23)/5) }
  157. VAR 
  158.   a,y : real;
  159.   e,i,j,p2 : integer;
  160.   neg, negexp, gtl : boolean;
  161.   digits: SET OF char;
  162.  
  163.     FUNCTION val ( a : real; ch: char ): real;
  164.     BEGIN
  165.       val := 10.0 * a + ord(ch)-z
  166.     END{val};
  167.  
  168. BEGIN
  169.   i := 1;
  170.   p := 0;
  171.   p2 := 0;
  172.   gtl := false;
  173.   digits := ['0'..'9'];
  174.   append(s,'%'); { safety character }
  175.   a := 0.0;
  176.   e := 0;
  177.   neg := (s[i]='-');
  178.   WHILE s[i]=' ' DO
  179.      i := i + 1;
  180.   IF (s[i]='+') OR (neg) THEN
  181.      i := i + 1;
  182.   WHILE s[i] IN digits DO BEGIN
  183.      IF s[i]='0' THEN
  184.         p2 := p2 + 1
  185.      ELSE BEGIN
  186.         p := p+p2+1;
  187.         p2 := 0;
  188.         gtl := true
  189.      END;
  190.      IF a<limit THEN
  191.         a := val ( a, s[i] )
  192.      ELSE
  193.     e := e + 1;
  194.       i := i + 1
  195.   END;
  196.   IF s[i]='.' THEN BEGIN
  197.      p := p + p2;
  198.      i := i + 1;
  199.      IF NOT (s[i] IN digits) THEN BEGIN
  200.         insert ( '0',s,i );
  201.         i := i + 1
  202.      END
  203.   END;
  204.   p2 := 0;
  205.   WHILE s[i]='0' DO BEGIN
  206.      p2 := p2 + 1;
  207.      IF a<limit THEN BEGIN
  208.         a := val ( a, s[i] );
  209.         e := e - 1
  210.      END;
  211.      i := i + 1
  212.   END;
  213.   IF gtl THEN
  214.      p := p + p2;
  215.   WHILE s[i] IN digits DO BEGIN
  216.      p := p+1;
  217.      IF a<limit THEN BEGIN
  218.         a := val ( a,s[i] );
  219.         e := e - 1
  220.      END;
  221.      i := i+1
  222.   END;
  223.   IF (s[i] IN ['E','e']) THEN BEGIN
  224.      i := i + 1;
  225.      j := 0;
  226.      negexp := (s[i]='-');
  227.      IF (s[i]='+') OR negexp THEN
  228.         i := i+1;
  229.      WHILE s[i] IN digits DO BEGIN
  230.         IF j<limit THEN
  231.        j := 10*j+ord(s[i]) - z;
  232.         i := i +1
  233.      END;
  234.      IF negexp THEN
  235.        e := e - j
  236.      ELSE
  237.        e := e + j
  238.   END;
  239.   y := a;
  240.   IF neg THEN
  241.      y := -y;
  242.   IF e<0 THEN
  243.      value := y/power(10,-e)
  244.   ELSE IF e<>0 THEN
  245.      value := y*power(10,e)
  246.   ELSE
  247.      value := y;
  248.   WHILE s[i]=' ' DO
  249.      i := i+1;
  250.   copy ( s,s,i,length(s)-i)
  251. END{value};
  252.  
  253.  
  254. {************* Write a real in appropriate format and return a blank *********}
  255.  
  256. FUNCTION f ( r:real ): char;
  257. CONST 
  258.   width = 23;
  259. VAR 
  260.   intpart,decimals,floating: integer;
  261. BEGIN
  262.   intpart := floor(log(r));
  263.   decimals := p - intpart - 1;
  264.   IF (r>10000.0) OR (r<0.0001) THEN {floating point}
  265.      write ( r:width )
  266.   ELSE IF decimals <= 0 THEN {integer}
  267.      write ( round(r): width )
  268.   ELSE  {fixed point}
  269.      write ( r:width:decimals );
  270.   f := ' '
  271. END;
  272.  
  273.  
  274. {*************** Special handling for temperatures ***************************}
  275.  
  276. PROCEDURE temperature ( VAR m: real; b: boolean; fact: integer );
  277. VAR d: integer;
  278. BEGIN
  279.   d := p - floor(log(m))-1;
  280.   m := m + fact * 273.15 + fact * 186.52 * ord( b );
  281.   p := d + floor(log(m)) + 1
  282. END;
  283.  
  284.  
  285. {*************** Find u in the table of units *******************************}
  286.  
  287. FUNCTION inlist: boolean;
  288. VAR    t: strng;
  289.  
  290.     FUNCTION match ( s: strng ): boolean;
  291.     BEGIN
  292.        match := ((u=s) OR (t=s))
  293.     END;
  294.  
  295. BEGIN
  296.   { start scan with left list }
  297.   leftside := true;
  298.   current := 1;
  299.   t := u;
  300.   IF length(t) > 1 THEN
  301.      delete(t,1,1);
  302.   WHILE (NOT(match(table[current].left))) and (current<=curtop) DO
  303.      current := current + 1;
  304.   IF current<=curtop THEN
  305.      inlist := true
  306.   ELSE BEGIN
  307.      { scan the right list starting at the top working towards the bottom }
  308.      current := curtop;
  309.      leftside := false;
  310.      WHILE (NOT(match(table[current].right))) and (current>0) DO
  311.         current := current - 1;
  312.      inlist := (current>0)
  313.   END
  314. END{inlist};
  315.  
  316.  
  317. {*************** Add correct metric prefix **********************************}
  318.  
  319. PROCEDURE prefix ( m: real; u: strng );
  320.  
  321.     PROCEDURE pref ( a: strng; fac,term: integer );
  322.     VAR i,range: integer;
  323.     BEGIN
  324.         range := floor ( log(m) / fac );
  325.       IF abs(range) > term THEN BEGIN
  326. {***         range := term * ( 1 - (2 * ord( (range<(-term)) )) ); ***}
  327.          range := term * ( 1 - (2 * ord( (range<=term) )) );
  328.       END;
  329.       m := m / power ( 10,(fac*range) );
  330.       IF range<>0 THEN BEGIN
  331.          a := a[range+term+1];
  332.         concat ( u,a,u );{ u := concat(a,u); }
  333.         writeln(f(m),u )
  334.       END
  335.     END{pref};
  336.  
  337. BEGIN{prefix}
  338.    IF pos('2',u)=2 THEN
  339.       pref(specp,2,3)
  340.    ELSE IF pos('3',u)=2 THEN
  341.       pref(specp,3,3)
  342.    ELSE
  343.       pref(normp,3,6)
  344. END{prefix};
  345.  
  346.  
  347. {******************** Convert to primary units *******************************}
  348.  
  349. PROCEDURE primary;
  350. VAR oldp: integer;
  351. BEGIN
  352.   WITH table[current] DO BEGIN
  353.      IF u='mpg' THEN
  354.     m := 1.0 / m;
  355.      IF length(u)=2 THEN
  356.     IF (u[1]='$') AND (u[2] IN ['F','C']) THEN
  357.        temperature(m,(u[2]='F'),1);
  358.      oldm := m;
  359.      oldf := factor;
  360.      oldp := p;
  361.      p := p + ord( norm(m) * norm(factor) >= 10.0 );
  362.      u := right;
  363.      m := m * factor;
  364.      writeln ( f(m),u );
  365.      prefix(m,u);
  366.      p := oldp;
  367.      leftside := false
  368.   END
  369. END{primary};
  370.  
  371.  
  372. {*************** check metric prefix and adjust if necessary *****************}
  373.  
  374. PROCEDURE normalize ( VAR m: real; VAR u: strng );
  375. VAR s: strng;
  376.  
  377.  PROCEDURE depref ( a: strng; fac,term: integer );
  378.  VAR    range,k  : integer;
  379.     needspref: boolean;
  380.  BEGIN
  381.    needspref := ( floor( log(m)/fac )<>0 );
  382.    IF pos(s,u)=2 THEN BEGIN
  383.       range := term+1;
  384.       FOR k:=1 TO length(a) DO BEGIN
  385.          IF u[1]=a[k] THEN
  386.         range := k-term-1
  387.       END;
  388.       k := range+term+1;
  389.       IF (k>=1) AND (k<=(term*2+1)) THEN BEGIN
  390.          m := m * power ( 10,fac*range );
  391.          delete(u,1,1);
  392.      writeln( f(m),u )
  393.       END
  394.       ELSE
  395.          writeln('illegal prefix ignored')
  396.    END;
  397.    IF needspref THEN
  398.       prefix(m,u)
  399.  END{depref};
  400.  
  401. BEGIN{normalize}
  402.   WITH table[current] DO BEGIN
  403.      IF leftside
  404.     THEN s := left
  405.     ELSE s := right
  406.   END;
  407.   IF pos('2',s) = 2 THEN
  408.      depref(specp,2,3)
  409.   ELSE IF pos('3',s)=2 THEN
  410.      depref(specp,3,3)
  411.   ELSE
  412.      depref(normp,3,6)
  413. END{normalize};
  414.  
  415.  
  416. {*************** Convert to customary or secondary units *********************}
  417.  
  418. PROCEDURE custandsec ( m: real );
  419. VAR oldp: integer;
  420. BEGIN
  421.   WITH table[current] DO BEGIN
  422.      oldp := p;
  423.      p := p + ord( norm(oldm) * norm(oldf/factor) >= 10.0 );
  424.      m := m / factor;
  425.      IF (u='m3/m') AND (current<=maxcust) THEN
  426.     m := 1.0 / m;
  427.      IF u='K' THEN
  428.     temperature ( m, (left[2]='F'), -1 );
  429.      writeln ( f(m), left );
  430.      IF current > maxcust THEN
  431.     prefix ( m, left );
  432.      p := oldp
  433.   END
  434. END{custandsec};
  435.  
  436.  
  437. {********** Pascal/Z compiler options **********}
  438. {$F-}{ floating point error checking OFF    }
  439. {$R-}{ range checking OFF            }
  440. {********** Pascal/Z compiler options **********}
  441.  
  442.  
  443. {*********************** Set up the table ***********************************}
  444.  
  445. PROCEDURE initialize;
  446.  
  447.  PROCEDURE data ( L,R: strng; f: real );
  448.  BEGIN
  449.    curtop := curtop+1;
  450.    WITH table[curtop] DO BEGIN
  451.       left := L;
  452.       right := R;
  453.       factor := f;
  454.    END
  455.  END;
  456.  
  457. BEGIN{initialize}
  458.    bell := chr(7);
  459.    WITH table[0] DO BEGIN
  460.       left := 'bottom';
  461.       right := 'bottom';
  462.       factor := 0.0
  463.    END;
  464.  
  465.   curtop := 0;
  466.  
  467.        { CUSTOMARY UNITS/PRIMARY UNITS/CONVERSION FACTOR }
  468.   data ( '$F',         'K',        5.5556e-1 );
  469.   data ( 'mpg',         'm3/m',    2.352e-6 );
  470.   data ( 'horsepower',     'W',        7.355e2 );
  471.   data ( 'inch of mercury', 'Pa',    3.37685e3 );
  472.   data ( 'mph',         'm/s',        4.4704e-1 );
  473.   data ( 'yard',     'm',        9.144e-1 );
  474.   data ( 'yard2',     'm2',        8.361274e-1 );
  475.   data ( 'acre',     'm2',        4047.0 );
  476.   data ( 'barrel',     'm3',        0.159 );
  477.   data ( 'kCal',     'J',        4.1868e3 );
  478.   data ( 'BTU',         'J',        1055.0 );
  479.   data ( 'Curie',     'Bq',        3.7e10 );
  480.  
  481.   maxcust := curtop;
  482.  
  483.        { SECONDARY UNITS/PRIMARY UNITS/CONVERSION FACTOR }
  484.   data ( 'L',         'm3',        1.0e-3 );
  485.   data ( 'N/m2',     'Pa',        1.0 );
  486.   data ('L/100 km',     'm3/m',    1.0e-8 );
  487.   data ( 'm/h',         'm/s',        2.777e-4 );
  488.   data ('kW-h',         'J',        3.6e6 );
  489.   data ('$C',         'K',        1.0 );
  490.   data ('N*m',         'J',        1.0 );
  491.  
  492.   data ( 'top','top', 0.0 ); { strings left & right must be initted }
  493.  
  494.   top := curtop
  495. END{initialize};
  496.  
  497.  
  498. {*************** Main subprograms *******************************************}
  499.  
  500. PROCEDURE give_help;
  501. { WRITTEN BY:    Raymond E. Penley    }
  502. { DATE WRITTEN:    Dec 15, 1982        }
  503. BEGIN
  504. writeln;
  505. writeln(
  506.  '   The program distinguishes rigorously between customary units,');
  507. writeln(
  508.  'primary metric units, and secondary metric units. By "primary" metric');
  509. writeln(
  510.  'units is meant the System International (SI) base units such as metre,');
  511. writeln(
  512.  'kilogram, kelvin, and so on, as well as the derived units such as');
  513. writeln(
  514.  'watt, newton, m/s, pascal, and volt.  By "secondary" metric units is');
  515. writeln(
  516.  'meant units like degree C and km/h which are accepted but not part of');
  517. writeln(
  518.  'SI, along with the formulas for derived units with special names, eg.');
  519. writeln(
  520.  'kg m/s as the formulas for the newton.');
  521. writeln;
  522. writeln(
  523.  '    Primary units may be converted to customary by using the');
  524. writeln(
  525.  '"c" command, while the "s" command converts them to secondary units.');
  526. writeln;
  527. writeln('Enter commands like:');
  528. writeln('Measure and unit>>5700 kJ');
  529. writeln('           5.70000E+06 J');
  530. writeln('                   5.7 MJ');
  531. writeln;
  532. writeln('Measure and unit>>secondary');
  533. writeln('           5.70000E+06 N*m');
  534. writeln('                   5.7 MN*m');
  535. writeln;  
  536. END{give_help};
  537.  
  538.  
  539. PROCEDURE commands;
  540. VAR i: integer;
  541. BEGIN
  542.   CASE line[1] OF
  543.     'F','f':
  544.     finished := true;
  545.  
  546.     'S','s':
  547.     IF (inlist) AND (current>maxcust) AND  (NOT leftside) THEN
  548.            custandsec(m);
  549.  
  550.     'H','h':
  551.     give_help;
  552.  
  553.     'C','c':
  554.     BEGIN
  555.            curtop := maxcust;
  556.            IF inlist THEN
  557.              custandsec ( m );
  558.            curtop := top
  559.         END;
  560.  
  561.     'L','l':
  562.     BEGIN
  563.        writeln( 'CUSTOMARY UNITS  PRIMARY UNITS  CONVERSION FACTOR' );
  564.        FOR i:=1 TO maxcust DO BEGIN
  565.               WITH table[i] DO
  566.                 writeln ( left:15, ' ',right:15,' ',factor:15)
  567.        END;
  568.        writeln;
  569.        writeln( 'SECONDARY UNITS  PRIMARY UNITS  CONVERSION FACTOR' );
  570.        FOR i:=maxcust+1 TO top-1 DO BEGIN
  571.               WITH table[i] DO
  572.                 writeln ( left:15, ' ',right:15,' ',factor:15)
  573.        END;
  574.     END
  575.    ELSE:
  576.     BEGIN
  577.        writeln;
  578.        writeln( CmdLine )
  579.     END
  580.   END{case};
  581.   writeln
  582. END{commands};
  583.  
  584.  
  585. PROCEDURE process;
  586. BEGIN
  587.   m := value ( line,p );
  588.   u := line;
  589.   oldf := 1.0;
  590.   IF NOT inlist THEN
  591.      writeln(bell, 'unit not available')
  592.   ELSE BEGIN
  593.      IF (current > maxcust) OR (NOT leftside) THEN
  594.         normalize ( m,u );
  595.      IF leftside THEN
  596.     primary
  597.   END;
  598.   writeln
  599. END{process};
  600.  
  601.  
  602. BEGIN { SuperMetrics }
  603.   finished := false;
  604.   initialize;
  605.   writeln(' ':22, 'SUPERMETRIC CONVERSION PROGRAM');
  606.   writeln; writeln;
  607.   writeln; writeln;
  608.   writeln ( CmdLine );
  609.   REPEAT
  610.     writeln;
  611.     write('Measure and unit >>');
  612.     readln(line);
  613.     IF (line[1] IN ['0'..'9','+','-']) THEN
  614.        process
  615.     ELSE
  616.        commands
  617.   UNTIL finished
  618. END.
  619.