<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P150-fixed-width-integers/ | updated: 2026-10-18 -->

# P150 Fixed-width integers

A string hash, a binary-search midpoint, factorials and a division in Python's integers and in a 32-bit int as C and Java have it: where it wraps, where it turns negative, and where the two divisions part.

EML-P project `projects/fixed-width-integers` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/fixed-width-integers` runs it and `eml project verify projects/fixed-width-integers` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: fixed-width-overflow (https://efficientnewlanguage.org/cases/238-fixed-width-overflow/), integer-division-sign (https://efficientnewlanguage.org/cases/239-integer-division-sign/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
1

1
toolongtext123
1
a!b
2

2
-1
2
2147483648
2
100

2
100
50
2
100
x
3

3
0
3
21
3
x
4

4
10000
4
7

4
7
0
4
7
x
4
-
5
```

Screen:

```text
== Fixed-width integers ==
Arithmetic in Python's integers and in a 32-bit int as C and Java have it.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 0
Pick a number from 1 to 5.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> x
Pick a number from 1 to 5.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 1
text (letters, digits, space . - _, up to 12)> 
Cancelled.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 1
text (letters, digits, space . - _, up to 12)> toolongtext123
Type 1 to 12 letters, digits, spaces, dots, dashes or underscores.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 1
text (letters, digits, space . - _, up to 12)> a!b
Type 1 to 12 letters, digits, spaces, dots, dashes or underscores.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 
Cancelled.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> -1
Type a whole number from 0 to 2147483647.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 2147483648
Type a whole number from 0 to 2147483647.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 100
hi (lo to 2147483647)> 
Cancelled.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 100
hi (lo to 2147483647)> 50
Type a whole number from 100 to 2147483647.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 100
hi (lo to 2147483647)> x
Type a whole number from 100 to 2147483647.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 3
up to (1 to 20)> 
Cancelled.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 3
up to (1 to 20)> 0
Type a whole number from 1 to 20.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 3
up to (1 to 20)> 21
Type a whole number from 1 to 20.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 3
up to (1 to 20)> x
Type a whole number from 1 to 20.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 
Cancelled.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 10000
Type a whole number from -9999 to 9999.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 7
b (-9999 to 9999, not 0)> 
Cancelled.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 7
b (-9999 to 9999, not 0)> 0
Type a whole number from -9999 to 9999, not 0.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 7
b (-9999 to 9999, not 0)> x
Type a whole number from -9999 to 9999, not 0.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> -
Type a whole number from -9999 to 9999.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 5
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
hello
1
Hello world
2
1500000000
2000000000
2
10
20
3
20
4
-7
2
4
7
2
4
-7
-2
4
7
-2
5
```

Screen:

```text
== Fixed-width integers ==
Arithmetic in Python's integers and in a 32-bit int as C and Java have it.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 1
text (letters, digits, space . - _, up to 12)> hello
h = h * 31 + code, one character at a time:
  char                    Python        32-bit
  'h'                        104           104
  'e'                       3325          3325
  'l'                     103183        103183
  'l'                    3198781       3198781
  'o'                   99162322      99162322
The hash fits in 32 bits: both give the same number.
Bucket of 16: Python hash % 16 = 2; C, with its hash, hash % 16 = 2; written ((hash % 16) + 16) % 16, 2.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 1
text (letters, digits, space . - _, up to 12)> Hello world
h = h * 31 + code, one character at a time:
  char                    Python        32-bit
  'H'                         72            72
  'e'                       2333          2333
  'l'                      72431         72431
  'l'                    2245469       2245469
  'o'                   69609650      69609650
  ' '                 2157899182   -2137068114   <- wraps here
  'w'                66894874761   -1824601975
  'o'              2073741117702    -728086266
  'r'             64285974648876   -1095837652
  'l'           1992865214115264     388771264
  'd'          61778821637573284    -832992604
The 32-bit hash is negative, -832992604, though every code is positive.
Bucket of 16: Python hash % 16 = 4; C, with its hash, hash % 16 = -12; written ((hash % 16) + 16) % 16, 4.
C's remainder takes the sign of the hash: a negative index, outside the table.
The safe index matches Python's because 16 divides 2^32: the wrap keeps the last four bits.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 1500000000
hi (lo to 2147483647)> 2000000000
  Python (lo + hi) // 2:        1750000000
  32-bit (lo + hi) / 2:         -397483648
  32-bit lo + (hi - lo) / 2:    1750000000
lo + hi is 3500000000, past 2147483647: it wraps, and the midpoint comes out -397483648. Written lo + (hi - lo) / 2, nothing overflows.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 2
lo (0 to 2147483647)> 10
hi (lo to 2147483647)> 20
  Python (lo + hi) // 2:        15
  32-bit (lo + hi) / 2:         15
  32-bit lo + (hi - lo) / 2:    15
lo + hi fits in 32 bits: all three agree.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 3
up to (1 to 20)> 20
    k          k! in Python    in 32 bits
    1                     1             1
    2                     2             2
    3                     6             6
    4                    24            24
    5                   120           120
    6                   720           720
    7                  5040          5040
    8                 40320         40320
    9                362880        362880
   10               3628800       3628800
   11              39916800      39916800
   12             479001600     479001600
   13            6227020800    1932053504   wrapped
   14           87178291200    1278945280   wrapped
   15         1307674368000    2004310016   wrapped
   16        20922789888000    2004189184   wrapped
   17       355687428096000    -288522240   negative
   18      6402373705728000    -898433024   negative
   19    121645100408832000     109641728   wrapped
   20   2432902008176640000   -2102132736   negative
13! is the first that does not fit: from there the 32-bit column is a plausible number and wrong.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> -7
b (-9999 to 9999, not 0)> 2
  Python, toward minus infinity: q -4, r 1; q * b + r = -7
  C and Java, toward zero:       q -3, r -1; q * b + r = -7
Both keep q * b + r = a and differ by one in q: the signs differ and the division is not exact.
As an index into 2 slots, Python's r is slot 1; C's is -1, outside them.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 7
b (-9999 to 9999, not 0)> 2
  Python, toward minus infinity: q 3, r 1; q * b + r = 7
  C and Java, toward zero:       q 3, r 1; q * b + r = 7
The two agree: the quotient is exact, or a and b have the same sign.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> -7
b (-9999 to 9999, not 0)> -2
  Python, toward minus infinity: q 3, r -1; q * b + r = -7
  C and Java, toward zero:       q 3, r -1; q * b + r = -7
The two agree: the quotient is exact, or a and b have the same sign.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 4
a (-9999 to 9999)> 7
b (-9999 to 9999, not 0)> -2
  Python, toward minus infinity: q -4, r -1; q * b + r = 7
  C and Java, toward zero:       q -3, r 1; q * b + r = 7
Both keep q * b + r = a and differ by one in q: the signs differ and the division is not exact.

1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit
choice> 5
Bye.
```

## Modules

### main.eml

```eml
# P150 fixed-width integers: a string hash, a midpoint, factorials and a
# division, each in Python's integers and in a 32-bit int, and where the two
# part.
import width

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def pad(s, n):
    s => out
    while len(out) < n:
        out + " " => out
    return out

def lpad(s, n):
    s => out
    while len(out) < n:
        " " + out => out
    return out

def number(t, most):
    # [True, the value] for an integer of at most most digits, a - allowed
    # in front, or [False, 0]
    if len(t) == 0:
        return [False, 0]
    0 => start
    if t[0] == "-":
        1 => start
    if len(t) == start or len(t) - start > most:
        return [False, 0]
    0 => v
    for c in t[start:len(t)]:
        if c < "0" or c > "9":
            return [False, 0]
        v * 10 + int(c) => v
    if start == 1:
        return [True, 0 - v]
    return [True, v]

def hash_text():
    plain(input("text (letters, digits, space . - _, up to 12)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    if len(t) > 12:
        "Type 1 to 12 letters, digits, spaces, dots, dashes or underscores." ^0
        return
    for c in t:
        if width.code(c) < 0:
            "Type 1 to 12 letters, digits, spaces, dots, dashes or underscores." ^0
            return
    width.hash_steps(t) => st
    ("h = h * 31 + code, one character at a time:") ^0
    ("  " + pad("char", 6) + lpad("Python", 24) + lpad("32-bit", 14)) ^0
    False => parted
    for x in st:
        ("  " + pad("'" + x[0] + "'", 6) + lpad(str(x[1]), 24) + lpad(str(x[2]), 14)) => line
        if x[1] != x[2] and not parted:
            line + "   <- wraps here" => line
            True => parted
        line ^0
    st[len(st) - 1] => last
    last[1] => h
    last[2] => w
    if h == w:
        "The hash fits in 32 bits: both give the same number." ^0
    elif w < 0:
        ("The 32-bit hash is negative, " + str(w) + ", though every code is positive.") ^0
    else:
        ("The 32-bit hash is " + str(w) + ", a plausible number and not Python's.") ^0
    width.floor_mod(h, 16) => a
    width.trunc_mod(w, 16) => b
    width.trunc_mod(width.trunc_mod(w, 16) + 16, 16) => c
    ("Bucket of 16: Python hash % 16 = " + str(a) + "; C, with its hash, hash % 16 = " + str(b) + "; written ((hash % 16) + 16) % 16, " + str(c) + ".") ^0
    if b < 0:
        "C's remainder takes the sign of the hash: a negative index, outside the table." ^0
    if h != w:
        "The safe index matches Python's because 16 divides 2^32: the wrap keeps the last four bits." ^0

def midpoint():
    plain(input("lo (0 to 2147483647)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    number(t, 10) => a
    if not a[0] or a[1] < 0 or a[1] > 2147483647:
        "Type a whole number from 0 to 2147483647." ^0
        return
    plain(input("hi (lo to 2147483647)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    number(t, 10) => b
    if not b[0] or b[1] < a[1] or b[1] > 2147483647:
        ("Type a whole number from " + str(a[1]) + " to 2147483647.") ^0
        return
    width.midpoints(a[1], b[1]) => m
    ("  Python (lo + hi) // 2:        " + str(m[0])) ^0
    ("  32-bit (lo + hi) / 2:         " + str(m[1])) ^0
    ("  32-bit lo + (hi - lo) / 2:    " + str(m[2])) ^0
    if m[1] != m[0]:
        ("lo + hi is " + str(a[1] + b[1]) + ", past 2147483647: it wraps, and the midpoint comes out " + str(m[1]) + ". Written lo + (hi - lo) / 2, nothing overflows.") ^0
    else:
        "lo + hi fits in 32 bits: all three agree." ^0

def factorials():
    plain(input("up to (1 to 20)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    number(t, 2) => n
    if not n[0] or n[1] < 1 or n[1] > 20:
        "Type a whole number from 1 to 20." ^0
        return
    ("  " + lpad("k", 3) + lpad("k! in Python", 22) + lpad("in 32 bits", 14)) ^0
    0 => first
    for x in width.factorials(n[1]):
        ("  " + lpad(str(x[0]), 3) + lpad(str(x[1]), 22) + lpad(str(x[2]), 14)) => line
        if x[1] != x[2]:
            if first == 0:
                x[0] => first
            if x[2] < 0:
                line + "   negative" => line
            else:
                line + "   wrapped" => line
        line ^0
    if first == 0:
        "Every one fits in 32 bits." ^0
    else:
        (str(first) + "! is the first that does not fit: from there the 32-bit column is a plausible number and wrong.") ^0

def divide():
    plain(input("a (-9999 to 9999)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    number(t, 4) => a
    if not a[0]:
        "Type a whole number from -9999 to 9999." ^0
        return
    plain(input("b (-9999 to 9999, not 0)> ")) => t
    if t == "":
        "Cancelled." ^0
        return
    number(t, 4) => b
    if not b[0] or b[1] == 0:
        "Type a whole number from -9999 to 9999, not 0." ^0
        return
    a[1] => x
    b[1] => y
    width.floor_div(x, y) => q1
    width.floor_mod(x, y) => r1
    width.trunc_div(x, y) => q2
    width.trunc_mod(x, y) => r2
    ("  Python, toward minus infinity: q " + str(q1) + ", r " + str(r1) + "; q * b + r = " + str(q1 * y + r1)) ^0
    ("  C and Java, toward zero:       q " + str(q2) + ", r " + str(r2) + "; q * b + r = " + str(q2 * y + r2)) ^0
    if q1 == q2:
        "The two agree: the quotient is exact, or a and b have the same sign." ^0
    else:
        "Both keep q * b + r = a and differ by one in q: the signs differ and the division is not exact." ^0
        if y > 0:
            ("As an index into " + str(y) + " slots, Python's r is slot " + str(r1) + "; C's is " + str(r2) + ", outside them.") ^0

"== Fixed-width integers ==" ^0
"Arithmetic in Python's integers and in a 32-bit int as C and Java have it." ^0
True => running
while running:
    "" ^0
    "1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        hash_text()
    elif choice == "2":
        midpoint()
    elif choice == "3":
        factorials()
    elif choice == "4":
        divide()
    elif choice == "5":
        False => running
    else:
        "Pick a number from 1 to 5." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import width

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def pad(s, n):
    out = s
    while len(out) < n:
        out = out + " "
    return out

def lpad(s, n):
    out = s
    while len(out) < n:
        out = " " + out
    return out

def number(t, most):
    if len(t) == 0:
        return [False, 0]
    start = 0
    if t[0] == "-":
        start = 1
    if len(t) == start or len(t) - start > most:
        return [False, 0]
    v = 0
    for c in t[start:len(t)]:
        if c < "0" or c > "9":
            return [False, 0]
        v = v * 10 + int(c)
    if start == 1:
        return [True, 0 - v]
    return [True, v]

def hash_text():
    t = plain(input("text (letters, digits, space . - _, up to 12)> "))
    if t == "":
        print("Cancelled.")
        return
    if len(t) > 12:
        print("Type 1 to 12 letters, digits, spaces, dots, dashes or underscores.")
        return
    for c in t:
        if width.code(c) < 0:
            print("Type 1 to 12 letters, digits, spaces, dots, dashes or underscores.")
            return
    st = width.hash_steps(t)
    print("h = h * 31 + code, one character at a time:")
    print("  " + pad("char", 6) + lpad("Python", 24) + lpad("32-bit", 14))
    parted = False
    for x in st:
        line = "  " + pad("'" + x[0] + "'", 6) + lpad(str(x[1]), 24) + lpad(str(x[2]), 14)
        if x[1] != x[2] and not parted:
            line = line + "   <- wraps here"
            parted = True
        print(line)
    last = st[len(st) - 1]
    h = last[1]
    w = last[2]
    if h == w:
        print("The hash fits in 32 bits: both give the same number.")
    elif w < 0:
        print("The 32-bit hash is negative, " + str(w) + ", though every code is positive.")
    else:
        print("The 32-bit hash is " + str(w) + ", a plausible number and not Python's.")
    a = width.floor_mod(h, 16)
    b = width.trunc_mod(w, 16)
    c = width.trunc_mod(width.trunc_mod(w, 16) + 16, 16)
    print("Bucket of 16: Python hash % 16 = " + str(a) + "; C, with its hash, hash % 16 = " + str(b) + "; written ((hash % 16) + 16) % 16, " + str(c) + ".")
    if b < 0:
        print("C's remainder takes the sign of the hash: a negative index, outside the table.")
    if h != w:
        print("The safe index matches Python's because 16 divides 2^32: the wrap keeps the last four bits.")

def midpoint():
    t = plain(input("lo (0 to 2147483647)> "))
    if t == "":
        print("Cancelled.")
        return
    a = number(t, 10)
    if not a[0] or a[1] < 0 or a[1] > 2147483647:
        print("Type a whole number from 0 to 2147483647.")
        return
    t = plain(input("hi (lo to 2147483647)> "))
    if t == "":
        print("Cancelled.")
        return
    b = number(t, 10)
    if not b[0] or b[1] < a[1] or b[1] > 2147483647:
        print("Type a whole number from " + str(a[1]) + " to 2147483647.")
        return
    m = width.midpoints(a[1], b[1])
    print("  Python (lo + hi) // 2:        " + str(m[0]))
    print("  32-bit (lo + hi) / 2:         " + str(m[1]))
    print("  32-bit lo + (hi - lo) / 2:    " + str(m[2]))
    if m[1] != m[0]:
        print("lo + hi is " + str(a[1] + b[1]) + ", past 2147483647: it wraps, and the midpoint comes out " + str(m[1]) + ". Written lo + (hi - lo) / 2, nothing overflows.")
    else:
        print("lo + hi fits in 32 bits: all three agree.")

def factorials():
    t = plain(input("up to (1 to 20)> "))
    if t == "":
        print("Cancelled.")
        return
    n = number(t, 2)
    if not n[0] or n[1] < 1 or n[1] > 20:
        print("Type a whole number from 1 to 20.")
        return
    print("  " + lpad("k", 3) + lpad("k! in Python", 22) + lpad("in 32 bits", 14))
    first = 0
    for x in width.factorials(n[1]):
        line = "  " + lpad(str(x[0]), 3) + lpad(str(x[1]), 22) + lpad(str(x[2]), 14)
        if x[1] != x[2]:
            if first == 0:
                first = x[0]
            if x[2] < 0:
                line = line + "   negative"
            else:
                line = line + "   wrapped"
        print(line)
    if first == 0:
        print("Every one fits in 32 bits.")
    else:
        print(str(first) + "! is the first that does not fit: from there the 32-bit column is a plausible number and wrong.")

def divide():
    t = plain(input("a (-9999 to 9999)> "))
    if t == "":
        print("Cancelled.")
        return
    a = number(t, 4)
    if not a[0]:
        print("Type a whole number from -9999 to 9999.")
        return
    t = plain(input("b (-9999 to 9999, not 0)> "))
    if t == "":
        print("Cancelled.")
        return
    b = number(t, 4)
    if not b[0] or b[1] == 0:
        print("Type a whole number from -9999 to 9999, not 0.")
        return
    x = a[1]
    y = b[1]
    q1 = width.floor_div(x, y)
    r1 = width.floor_mod(x, y)
    q2 = width.trunc_div(x, y)
    r2 = width.trunc_mod(x, y)
    print("  Python, toward minus infinity: q " + str(q1) + ", r " + str(r1) + "; q * b + r = " + str(q1 * y + r1))
    print("  C and Java, toward zero:       q " + str(q2) + ", r " + str(r2) + "; q * b + r = " + str(q2 * y + r2))
    if q1 == q2:
        print("The two agree: the quotient is exact, or a and b have the same sign.")
    else:
        print("Both keep q * b + r = a and differ by one in q: the signs differ and the division is not exact.")
        if y > 0:
            print("As an index into " + str(y) + " slots, Python's r is slot " + str(r1) + "; C's is " + str(r2) + ", outside them.")

print("== Fixed-width integers ==")
print("Arithmetic in Python's integers and in a 32-bit int as C and Java have it.")
running = True
while running:
    print("")
    print("1) hash a text  2) a midpoint  3) factorials  4) divide  5) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        hash_text()
    elif choice == "2":
        midpoint()
    elif choice == "3":
        factorials()
    elif choice == "4":
        divide()
    elif choice == "5":
        running = False
    else:
        print("Pick a number from 1 to 5.")
print("Bye.")
```

### width.eml

```eml
# P150 fixed-width integers - the same arithmetic in Python's integers and
# in a 32-bit int as C and Java have it. As in the corpus case
# fixed-width-overflow, Python never overflows and a fixed-width language
# wraps, quietly, to a plausible number - a hash, a midpoint, a factorial -
# and can wrap to the wrong sign. As in the corpus case
# integer-division-sign, Python's division rounds toward minus infinity and
# C's toward zero, and the remainder follows; the two disagree exactly on
# the negative values that wrap backwards, a negative hash among them. Two's
# complement is arithmetic mod 2^32 with the top half read as negative, so
# it is written with %.

def two31():
    return 2147483648

def two32():
    return 4294967296

def wrap32(n):
    n % two32() => m
    if m >= two31():
        return m - two32()
    return m

def code(c):
    # the character code of a letter, digit, space, dot, dash or underscore
    "0123456789" => d
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" => up
    "abcdefghijklmnopqrstuvwxyz" => low
    0 => i
    while i < 26:
        if i < 10 and d[i] == c:
            return 48 + i
        if up[i] == c:
            return 65 + i
        if low[i] == c:
            return 97 + i
        i + 1 => i
    if c == " ":
        return 32
    if c == "-":
        return 45
    if c == ".":
        return 46
    if c == "_":
        return 95
    return 0 - 1

def hash_steps(s):
    # [character, the hash in Python, the hash in 32 bits] after each one:
    # h = h * 31 + code, as Java's String.hashCode
    0 => h
    0 => w
    [] => out
    for c in s:
        h * 31 + code(c) => h
        wrap32(w * 31 + code(c)) => w
        out + [[c, h, w]] => out
    return out

def floor_div(a, b):
    # Python's a // b, written without it: the truncated quotient, one less
    # when the signs differ and it is not exact
    trunc_div(a, b) => q
    if q * b != a and (a < 0) != (b < 0):
        q - 1 => q
    return q

def trunc_div(a, b):
    # C's a / b on ints: toward zero; exact for big values too, the float
    # quotient corrected step by step
    0 => neg
    a => x
    b => y
    if x < 0:
        0 - x => x
        1 - neg => neg
    if y < 0:
        0 - y => y
        1 - neg => neg
    int(x / y) => q
    while q * y > x:
        q - 1 => q
    while (q + 1) * y <= x:
        q + 1 => q
    if neg == 1:
        return 0 - q
    return q

def floor_mod(a, b):
    return a - floor_div(a, b) * b

def trunc_mod(a, b):
    return a - trunc_div(a, b) * b

def midpoints(lo, hi):
    # [Python (lo + hi) // 2, 32-bit (lo + hi) / 2, 32-bit lo + (hi - lo) / 2]
    return [floor_div(lo + hi, 2), trunc_div(wrap32(lo + hi), 2), wrap32(lo + trunc_div(wrap32(hi - lo), 2))]

def factorials(n):
    # [k, k! in Python, k! in 32 bits] for k from 1 to n
    1 => f
    1 => w
    [] => out
    1 => k
    while k <= n:
        f * k => f
        wrap32(w * k) => w
        out + [[k, f, w]] => out
        k + 1 => k
    return out
```

Python projection of width.eml:

```python
def two31():
    return 2147483648

def two32():
    return 4294967296

def wrap32(n):
    m = n % two32()
    if m >= two31():
        return m - two32()
    return m

def code(c):
    d = "0123456789"
    up = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    low = "abcdefghijklmnopqrstuvwxyz"
    i = 0
    while i < 26:
        if i < 10 and d[i] == c:
            return 48 + i
        if up[i] == c:
            return 65 + i
        if low[i] == c:
            return 97 + i
        i = i + 1
    if c == " ":
        return 32
    if c == "-":
        return 45
    if c == ".":
        return 46
    if c == "_":
        return 95
    return 0 - 1

def hash_steps(s):
    h = 0
    w = 0
    out = []
    for c in s:
        h = h * 31 + code(c)
        w = wrap32(w * 31 + code(c))
        out = out + [[c, h, w]]
    return out

def floor_div(a, b):
    q = trunc_div(a, b)
    if q * b != a and (a < 0) != (b < 0):
        q = q - 1
    return q

def trunc_div(a, b):
    neg = 0
    x = a
    y = b
    if x < 0:
        x = 0 - x
        neg = 1 - neg
    if y < 0:
        y = 0 - y
        neg = 1 - neg
    q = int(x / y)
    while q * y > x:
        q = q - 1
    while (q + 1) * y <= x:
        q = q + 1
    if neg == 1:
        return 0 - q
    return q

def floor_mod(a, b):
    return a - floor_div(a, b) * b

def trunc_mod(a, b):
    return a - trunc_div(a, b) * b

def midpoints(lo, hi):
    return [floor_div(lo + hi, 2), trunc_div(wrap32(lo + hi), 2), wrap32(lo + trunc_div(wrap32(hi - lo), 2))]

def factorials(n):
    f = 1
    w = 1
    out = []
    k = 1
    while k <= n:
        f = f * k
        w = wrap32(w * k)
        out = out + [[k, f, w]]
        k = k + 1
    return out
```

## README

# P150 - Fixed-width integers

The same arithmetic in Python's integers and in a 32-bit int as C and Java
have it: a string hash, a midpoint, factorials and a division.

- `main.eml` - the menu, a text hashed one character at a time, a
  midpoint, a table of factorials, and a division both ways
- `width.eml` - two's complement wrapping, character codes, the hash
  steps, division toward minus infinity and toward zero with their
  remainders, the three midpoints, and the factorials

How each part works:

- As in the corpus case `fixed-width-overflow`, Python never overflows and
  a 32-bit int wraps, quietly, to a number of the right type. Two's
  complement is arithmetic mod 2^32 with the top half read as negative,
  so it is written with %, there being no bitwise operators.
- The hash is h = h * 31 + code, as Java's String.hashCode. "Hello world"
  wraps at the space and ends at -832992604, negative though every code is
  positive. As in the corpus case `integer-division-sign`, C's remainder
  takes the sign of the hash: its bucket of 16 is -12, an index outside the
  table. Written ((hash % 16) + 16) % 16 it is 4, Python's bucket - because
  16 divides 2^32, the wrap keeps the last four bits.
- The midpoint of a binary search: (lo + hi) / 2 wraps once lo + hi passes
  2147483647, giving a negative midpoint; lo + (hi - lo) / 2 never does.
- Factorials: 13! is the first past 32 bits; from there the 32-bit column
  is plausible and wrong, and some of it negative.
- Division: Python rounds toward minus infinity and C toward zero. Both
  keep q * b + r = a; they differ, by one in q, exactly when the signs
  differ and the division is not exact, and then C's remainder is no slot
  of a ring of b.

What is checked: menu choices 1 to 5; a text of 1 to 12 letters, digits,
spaces, dots, dashes or underscores; lo from 0 to 2147483647 and hi from
lo; a factorial up to 1 to 20; a and b from -9999 to 9999, b not 0. An
empty answer cancels.

Sessions: `sessions/basic.in` gives:
- hello, which fits, and Hello world, which wraps;
- the midpoint of 1500000000 and 2000000000, and of 10 and 20;
- factorials to 20; -7 and 7 by 2, and -7 and 7 by -2.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- texts: a cancel, 14 characters and a!b;
- midpoints: a cancel, -1, 2147483648, then lo 100 with a cancel, 50 and
  x;
- factorials: a cancel, 0, 21 and x;
- divisions: a cancel, 10000, then a 7 with a cancel, 0 and x, and a lone
  minus sign.

Built on the verified corpus cases `fixed-width-overflow` (a hash, a
midpoint and a factorial in 32 bits, written with modulo) and
`integer-division-sign` (floor and truncating division, both against
q * b + r = a, and where they disagree).
