💾 Archived View for sdf.org › rsdoiel › blog › 2021 › 11 › 26 › Portable-Conversions-Integers.html captured on 2023-06-14 at 14:28:02.

View Raw

More Information

⬅️ Previous capture (2023-03-20)

-=-=-=-=-=-=-

<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="language" content="EN">
  <title>Portable-Conversions-Integers</title>

  <link rel="stylesheet" type="text/css"  href="/printfonts/print.css" media="print" />
  <link rel="stylesheet" type="text/css"  href="/webfonts/fonts.css" media="screen" />
  <link rel="stylesheet" type="text/css"  href="/css/site.css" media="screen" />
  <link rel="stylesheet" type="text/css"  href="/css/tables.css" media="screen" />
  <link title="RSS feed for rsdoiel's blog" rel="alternate" type="application/rss+xml" href="https://rsdoiel.github.io/rss.xml" />
  <meta name="keywords" content="Oberon, Modules, Types, conversion">
  <link rel="alternative" type="application/markdown" href="/blog/2021/11/26/Portable-Conversions-Integers.md">
  <link rel="search" type="application/opensearchdescription+xml"
        title="Robert's Rambling Search Engine"
        href="search.osdx">
</head>
<body>
<nav>
<ul>
<li><a href="/">R. S. Doiel</a></li>
<li><a href="/about.html">About</a></li>
<li><a href="/blog/">Blog</a></li>
<li><a href="/cv.html">CV</a></li>
<li><a href="https://github.com/rsdoiel">GitHub</a></li>
<li><a href="/library-terminology.html">Library Jargon</a></li>
<li><a href="/presentations.html">Presentations</a></li>
<li><a href="/projects.html">Projects</a></li>
<li><a href="/resume.html">Resume</a></li>
<li><a href="/search.html">Search</a></li>
<li><a href="/series/">Series</a></li>
</ul>
</nav>

<section>
  <article>
  <a data-pocket-label="pocket" data-pocket-count="none" class="pocket-btn" data-lang="en"></a>
<script type="text/javascript">!function(d,i){if(!d.getElementById(i)){var j=d.createElement("script");j.id=i;j.src="https://widgets.getpocket.com/v1/j/btn.js?v=1";var w=d.getElementById(i);d.body.appendChild(j);}}(document,"pocket-btn-js");</script>
<h1 id="portable-conversions-integers">Portable conversions
(Integers)</h1>
<p>By R. S. Doiel, 2021-11-26</p>
<p>An area in working with Oberon-07 on a POSIX machine that has proven
problematic is type conversion. In particular converting to and from
INTEGER or REAL and ASCII. None of the three compilers I am exploring
provide a common way of handling this. I’ve explored relying on C
libraries but that approach has it’s own set of problems. I’ve become
convinced a better approach is a pure Oberon-07 library that handles
type conversion with a minimum of assumptions about the implementation
details of the Oberon compiler or hardware. I’m calling my conversion
module “Types”. The name is short and descriptive and seems an
appropriate name for a module consisting of type conversion tests and
transformations. My initial implementation will focusing on converting
integers to and from ASCII.</p>
<h2 id="integer-to-ascii-and-back-again">INTEGER to ASCII and back
again</h2>
<p>I don’t want to rely on the representation of the INTEGER value in
the compiler or at the machine level. That has lead me to think in terms
of an INTEGER as a signed whole number.</p>
<p>The simplest case of converting to/from ASCII is the digits from zero
to nine (inclusive). Going from an INTEGER to an ASCII CHAR is just
looking up the offset of the character representing the “digit”. Like
wise going from ASCII CHAR to a INTEGER is a matter of mapping in the
reverse direction. Let’s call these procedures <code>DigitToChar</code>
and <code>CharToDigit*</code>.</p>
<p>Since INTEGER can be larger than zero through nine and CHAR can hold
non-digits I’m going to add two additional procedures for validating
inputs – <code>IsIntDigit</code> and <code>IsCharDigit</code>. Both
return TRUE if valid, FALSE if not.</p>
<p>For numbers larger than one digit I can use decimal right shift to
extract the ones column value or a left shift to reverse the process.
Let’s called these <code>IntShiftRight</code> and
<code>IntShiftLeft</code>. For shift right it’d be good to capture the
ones column being lost. For shift left it would be good to be able to
shift in a desired digit. That way you could shift/unshift to retrieve
to extract and put back values.</p>
<p>A draft definition for “Types” should look something like this.</p>
<pre><code>DEFINITION Types;

(* Check if an integer is a single digit, i.e. from 0 through 9 returns
   TRUE, otherwise FALSE *)
PROCEDURE IsIntDigit(x : INTEGER) : BOOLEAN;

(* Check if a CHAR is &quot;0&quot; through &quot;9&quot; and return TRUE, otherwise FALSE *)
PROCEDURE IsCharDigit(ch : CHAR) : BOOLEAN;

(* Convert digit 0 through 9 into an ASCII CHAR &quot;0&quot; through &quot;9&quot;,
   ok is TRUE if conversion successful, FALSE otherwise *)
PROCEDURE DigitToChar(x : INTEGER; VAR ch : CHAR; VAR ok : BOOLEAN);

(* Convert a CHAR &quot;0&quot; through &quot;9&quot; into a digit 0 through 9, ok
   is TRUE is conversion successful, FALSE otherwise *)
PROCEDURE CharToDigit(ch : CHAR; VAR x : INTEGER; VAR ok : BOOLEAN);

(* Shift an integer to the right (i.e. x * 0.1) set &quot;r&quot; to the
   value shifted out (ones column lost) and return the shifted value.
   E.g.  x becomes 12, r becomes 3.

       x := IntShiftRight(123, r);
   
 *)
PROCEDURE IntShiftRight(x : INTEGER; VAR r : INTEGER) : INTEGER;

(* Shift an integer to the left (i.e. x * 10) adding the value y
   after the shift.

   E.g. x before 123

       x := IntShiftRight(12, 3);

 *)
PROCEDURE IntShiftLeft(x, y : INTEGER) : INTEGER;

(* INTEGER to ASCII *)
PROCEDURE Itoa(src : INTEGER; VAR value : ARRAY OF CHAR; VAR ok : BOOLEAN);

(* ASCII to INTEGER *)
PROCEDURE Atoi(src : ARRAY OF CHAR; VAR value : INTEGER; VAR ok : BOOLEAN);

END Types.</code></pre>
<p>NOTE: Oberon-07 provides us the ORD and CHR built as part of the
language. These are for working with the encoding and decoding values as
integers. This is not the same thing as the meaning of “0” versus the
value of 0. Getting to and from the encoding to the meaning of the
presentation can be done with some simple arithmetic.</p>
<h2 id="putting-it-all-together">Putting it all together</h2>
<pre><code>(* DigitToChar converts an INTEGER less than to a character. E.g.
   0 should return &quot;0&quot;, 3 returns &quot;3&quot;, 0 returns &quot;9&quot; *)
PROCEDURE DigitToChar*(i : INTEGER) : CHAR;
BEGIN
  RETURN (CHR(ORD(&quot;0&quot;) + i))
END DigitToChar;

(* CharToDigit converts a single &quot;Digit&quot; character to an INTEGER value.
   E.g. &quot;0&quot; returns 0, &quot;3&quot; returns 3, &quot;9&quot; returns 9. *)
PROCEDURE CharToDigit(ch : CHAR) : INTEGER;
BEGIN
  RETURN (ORD(ch) - ORD(&quot;0&quot;))
END CharToDigit;</code></pre>
<p>This implementation is naive. It assumes the ranges of the input
values was already checked. In practice this is going to encourage
bugs.</p>
<p>In a language like Go or Python you can return multiple values (in
Python you can return a tuple). In Oberon-07 I could use a RECORD type
to do that but that feels a little too baroque. Oberon-07 like Oberon-2,
Oberon, Modula and Pascal does support “VAR” parameters. With a slight
modification to our procedure signatures I can support easy assertions
about the conversion. Let’s create two functional procedures
<code>IsIntDigit()</code> and <code>IsCharDigit()</code> then update our
<code>DigitToChar()</code> and <code>CharToDigit()</code> with an a “VAR
ok : BOOLEAN” parameter.</p>
<pre><code>(* IsIntDigit returns TRUE is the integer value is zero through nine *)
PROCEDURE IsIntDigit(i : INTEGER) : BOOLEAN;
BEGIN 
  RETURN ((i &gt;= 0) &amp; (i &lt;= 9))
END IsIntDigit;

(* IsCharDigit returns TRUE if character is zero through nine. *)
PROCEDURE IsCharDigit(ch : CHAR) : BOOLEAN;
BEGIN
  RETURN ((ch &gt;= &quot;0&quot;) &amp; (ch &lt;= &quot;9&quot;))
END IsCharDigit;

(* DigitToChar converts an INTEGER less than to a character. E.g.
   0 should return &quot;0&quot;, 3 returns &quot;3&quot;, 0 returns &quot;9&quot; *)
PROCEDURE DigitToChar*(i : INTEGER; VAR ok : BOOLEAN) : CHAR;
BEGIN
  ok := IsIntDigit(i);
  RETURN (CHR(ORD(&quot;0&quot;) + i))
END DigitToChar;

(* CharToDigit converts a single &quot;Digit&quot; character to an INTEGER value.
   E.g. &quot;0&quot; returns 0, &quot;3&quot; returns 3, &quot;9&quot; returns 9. *)
PROCEDURE CharToDigit(ch : CHAR; VAR ok : BOOLEAN) : INTEGER;
BEGIN
  ok := IsCharDigit(ch);
  RETURN (ORD(ch) - ORD(&quot;0&quot;))
END CharToDigit;</code></pre>
<p>What about values are greater nine? Here we can take advantage of our
integer shift procedures. <code>IntShiftRight</code> will move the
INTEGER value right reducing it’s magnitude (i.e. x * 0.1). It also
captures the ones column lost in the shift. Repeatedly calling
<code>IntShiftRight</code> will let us peal off the ones columns until
the value “x” is zero. <code>IntShiftLeft</code> shifts the integer to
the left meaning it raises it a magnitude (i.e. x * 10).
<code>IntShiftLeft</code> also rakes a value to shift in on the right
side of the number. In this way we can shift in a zero and get
<code>x * 10</code> or shift in another digit and get
<code>(x * 10) + y</code>. This means you can use
<code>IntShiftRight</code> and recover an <code>IntShiftLeft</code>.</p>
<pre><code>
(* IntShiftRight converts the input integer to a real, multiplies by 0.1
   and converts by to an integer. The value in the ones column is record
   in the VAR parameter r.  E.g. IntShiftRight(123) return 12, r is set to 3. *)
PROCEDURE IntShiftRight*(x : INTEGER; VAR r : INTEGER) : INTEGER;
  VAR i : INTEGER; isNeg : BOOLEAN;
BEGIN
  isNeg := (x &lt; 0);
  i := FLOOR(FLT(ABS(x)) * 0.1);
  r := ABS(x) - (i * 10);
  IF isNeg THEN
    i := i * (-1);
  END;
  RETURN i
END IntShiftRight;

(* IntShiftLeft multiples input value by 10 and adds y. E.g. IntShiftLeft(123, 4) return 1234 *)
PROCEDURE IntShiftLeft*(x, y : INTEGER) : INTEGER;
  VAR i : INTEGER; isNeg : BOOLEAN;
BEGIN
  isNeg := (x &lt; 0);
  i := (ABS(x) * 10) + y;
  IF isNeg THEN
    i := i * (-1);
  END;
  RETURN i
END IntShiftLeft;
</code></pre>
<p>I have what I need for implementing <code>Itoa</code> (integer to
ASCII).</p>
<pre><code>
(* Itoa converts an INTEGER to an ASCII string setting ok BOOLEAN to
   TRUE if value ARRAY OF CHAR holds the full integer, FALSE if
   value was too small to hold the integer value.  *)
PROCEDURE Itoa*(x : INTEGER; VAR value : ARRAY OF CHAR; ok : BOOLEAN);
  VAR i, j, k, l, minL : INTEGER; tmp : ARRAY BUFSIZE OF CHAR; isNeg : BOOLEAN;
BEGIN
  i := 0; j := 0; k := 0; l := LEN(value); isNeg := (x &lt; 0);
  IF isNeg THEN
    (* minimum string length for value is 3, negative sign, digit and 0X *)
    minL := 3;
  ELSE 
    (* minimum string length for value is 2, one digit and 0X *)
    minL := 2; 
  END;
  ok := (l &gt;= minL) &amp; (LEN(value) &gt;= LEN(tmp));
  IF ok THEN
    IF IsIntDigit(ABS(x)) THEN
      IF isNeg THEN
         value[i] := &quot;-&quot;; INC(i);
      END;
      value[i] := DigitToChar(ABS(x), ok); INC(i); value[i] := 0X;
    ELSE
      x := ABS(x); (* We need to work with the absolute value of x *)
      i := 0; tmp[i] := 0X;
      WHILE (x &gt;= 10) &amp; ok DO
        (* extract the ones columns *)
        x := IntShiftRight(x, k); (* a holds the shifted value, 
                                     &quot;k&quot; holds the ones column 
                                     value shifted out. *)
        (* write append k to our temp array holding values in
           reverse number magnitude *)
        tmp[i] := DigitToChar(k, ok); INC(i); tmp[i] := 0X;
      END;
      (* We now can convert the remaining &quot;ones&quot; column. *)
      tmp[i] := DigitToChar(x, ok); INC(i); tmp[i] := 0X;
      IF ok THEN
        (* now reverse the order of tmp string append each
           character to value *)
        i := 0; j := Strings.Length(tmp) - 2;
        IF isNeg THEN
          value[i] := &quot;-&quot;; INC(i);
        END;
        j := Strings.Length(tmp) - 1;
        WHILE (j &gt; -1) DO
          value[i]:= tmp[j]; 
          INC(i); DEC(j);
          value[i] := 0X;
        END;
        value[i] := 0X;
      END;
    END; 
  ELSE
    ok := FALSE;
  END;
END Itoa;
</code></pre>
<p>Integers in Oberon are signed. So I’ve chosen to capture the sign in
the <code>isNeg</code> variable. This lets me work with the absolute
value for the actual conversion. One failing in this implementation is I
don’t detect an overflow. Also notice that I am accumulating the
individual column values in reverse order (lowest magnitude first). That
is what I need a temporary buffer. I can then copy the values in reverse
order into the VAR ARRAY OF CHAR. Finally I also maintain the ok BOOLEAN
to track if anything went wrong.</p>
<p>When moving from an ASCII representation I can simplified the code by
having a local (to the module) procedure for generating magnitudes.</p>
<p>Going the other way I can simplify my <code>Atoi</code> if I have an
local to the module “magnitude” procedure.</p>
<pre><code>
(* magnitude takes x and multiplies it be 10^y, If y is positive zeros
   are appended to the right side (i.e. multiplied by 10). If y is
   negative then the result is shifted left (i.e.. multiplied by
   0.1 via IntShiftRight().).  The digit(s) shift to the fractional
   side of the decimal are ignored. *)
PROCEDURE magnitude(x, y : INTEGER) : INTEGER;
  VAR z, w : INTEGER;
BEGIN
  z := 1;
  IF y &gt;= 0 THEN
    WHILE y &gt; 0 DO
      z := IntShiftLeft(z, 0);
      DEC(y);
    END;
  ELSE
    WHILE y &lt; 0 DO
      x := IntShiftRight(x, w);
      INC(y);
    END;
  END;
  RETURN (x * z)
END magnitude;
</code></pre>
<p>And with that I can put together my <code>Atoi</code> (ASCII to
integer) procedure. I’ll need to add some sanity checks as well.</p>
<pre><code>
(* Atoi converts an ASCII string to a signed integer value
   setting the ok BOOLEAN to TRUE on success and FALSE on error. *)
PROCEDURE Atoi*(source : ARRAY OF CHAR; VAR value : INTEGER; VAR ok : BOOLEAN);
  VAR i, l, a, m: INTEGER; isNeg : BOOLEAN;
BEGIN
  (* &quot;i&quot; is the current CHAR position we&#39;re analyzing, &quot;l&quot; is the
     length of our string, &quot;a&quot; holds the accumulated value,
     &quot;m&quot; holds the current magnitude we&#39;re working with *)
  i := 0; l := Strings.Length(source);
  a := 0; m := l - 1; isNeg := FALSE; ok := TRUE;
  (* Validate magnitude and sign behavior *)
  IF (l &gt; 0) &amp; (source[0] = &quot;-&quot;) THEN
    INC(i); DEC(m);
    isNeg := TRUE;
  ELSIF (l &gt; 0) &amp; (source[0] = &quot;+&quot;) THEN
    INC(i); DEC(m);
  END;

  (* The accumulator should always hold a positive integer, if the
     sign flips we have overflow, ok should be set to FALSE *)
  ok := TRUE;
  WHILE (i &lt; l) &amp; ok DO
    a := a + magnitude(CharToDigit(source[i], ok), m);
    IF a &lt; 0 THEN
      ok := FALSE; (* we have an overflow condition *)
    END;
    DEC(m);
    INC(i);
  END;
  IF ok THEN
    IF (i = l) THEN
      IF isNeg THEN
        value := a * (-1);
      ELSE
        value := a;
      END;
    END;
  END;
END Atoi;
</code></pre>
<p>Here’s an example using the procedures.</p>
<p>Converting an integer 1234 to an string “1234”.</p>
<pre><code>
   x := 1234; s := &quot;&quot;; ok := FALSE;
   Types.Itoa(x, s, ok);
   IF ok THEN 
     Out.String(s); Out.String(&quot; = &quot;);
     Out.Int(x,1);Out.Ln;
   ELSE
     Out.String(&quot;Something went wrong&quot;);Out.Ln;
   END;
</code></pre>
<p>Converting a string “56789” to integer 56789.</p>
<pre><code>
   x := 0; src := &quot;56789&quot;; ok := FALSE;
   Types.Atoi(src, x, ok);
   IF ok THEN 
     Out.Int(x,1); Out.String(&quot; = &quot;); Out.String(s); 
     Out.Ln;
   ELSE
     Out.String(&quot;Something went wrong&quot;);Out.Ln;
   END;
</code></pre>
<h2 id="references-and-resources">References and resources</h2>
<p>Implementations for modules for this article are linked here <a
href="./Types.Mod">Types</a>, <a href="./TypesTest.Mod">TypesTest</a>
and <a href="./Tests.Mod">Tests</a>.</p>
<p>Expanded versions of the <code>Types</code> module will be available
as part of Artemis Project – <a
href="https://github.com/rsdoiel/Artemis">github.com/rsdoiel/Artemis</a>.</p>
<h2 id="previous">Previous</h2>
<ul>
<li><a href="../../11/22/Revisiting-Files.html">Revisiting
Files</a></li>
</ul>
  </article>
</section>

<footer>
<p>copyright © 2016 - 2023 R. S. Doiel<br /> <a
href="/rssfeed.html">RSS</a> feeds and website built with <a
href="https://rsdoiel.github.io/pttk">pttk</a>, Bash, Make and <a
href="https://pandoc.org">Pandoc</a>.</p>
</footer>
<!-- START: PrettyFi from https://github.com/google/code-prettify -->
<script>
/* We want to add the class "prettyprint" to all the pre elements */
var pre_list = document.querySelectorAll("pre");

pre_list.forEach(function(elem) {
    elem.classList.add("prettyprint");
    elem.classList.add("linenums");/**/
    elem.classList.add("json"); /**/
});
</script>
<style>
li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9
{
    color: #555;
    list-style-type: decimal;
}
</style>
<link rel="stylesheet" type="text/css" href="/css/prettify.css">
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_
prettify.js"></script>
<!--  END: PrettyFi from https://github.com/google/code-prettify -->

</body>
</html>