Raw File
arithmeticIBM.py
#!/usr/bin/env python3
'''
The author if this program (Ronald S. Burkey) declares that it is in the 
Public Domain and may be used or modified for any purpose whatever.

Filename:   arithmeticIBM.py
Purpose:    The intention here is to imitate several floating-point 
            (IBM 360 and IBM 7090/7094 single-precision)
            arithmetic types, to see if they can be used to solve 
            certain anomalies in arithmetic peformed by the original LVDC
            assembler by IBM FSD.
Contact:    Ron Burkey <info@sandroid.org>, The Virtual AGC Project
History:    2023-06-20 RSB   Began.

See the comments in arithmetic360.py for more explanation.  This program differs
only in that it models the internal floating-point format of the IBM 7090/7094
rather than the IBM 360.  The differences are:

                              360                7090
                            -------            -------
    Single-precision word   32 bits            36 bits
    Sign                     1 bit              1 bit
    Exponent                 7 bits             8 bits
    Exponent base             16                  2
    Mantissa                24 bits            27 bits
'''

import sys

test = False
check = False
systemType = 7090 # The other choice is 360
for parm in sys.argv[1:]:
    if parm == "--test":
        test = True
    elif parm == "--check":
        check = True
    elif parm.startswith("--system="):
        systemType = int(parm[9:])

if systemType == 7090:
    exponentBaseBits = 1
    biasExponent = 128
    numMantissaBits = 27
elif systemType == 360:
    exponentBaseBits = 4
    biasExponent = 64
    numMantissaBits = 24
else:
    print("Unknown system type")
    sys.exit(1)
print("System type IBM %d.  Exponent base bits %d, bias %d.  Mantissa bits %d." % \
      (systemType, exponentBaseBits, biasExponent, numMantissaBits))
exponentBase = 2**exponentBaseBits
maxExponent = biasExponent - 1
minExponent = -biasExponent
maxFraction1 = 2**numMantissaBits
maxFraction = maxFraction1 - 1
floorFraction = 2**(numMantissaBits-exponentBaseBits)
zero = { "negative": False, "exponent": 0, "fraction": 0}

# Accepts a Python integer or floating-point value and returns an IBM
# single-precision floating-point value, or else the value None on error.
def toIBM(value):
    if value == "P.CSR":
        value = "4063.492"
    try:
        value = float(value)
    except:
        return None
    if value == 0:
        return zero
    negative = False
    if value < 0:
        negative = True
        value = -value
    exponent = 0
    while value >= 1:
        exponent += 1
        value /= exponentBase
    while True:
        bigger = value * exponentBase
        if bigger >= 1:
            break
        value = bigger
        exponent -= 1
    if exponent > maxExponent:
        return None
    if exponent < minExponent:
        return zero
    fraction = int(0.5 + value * maxFraction1)
    return {"negative": negative, "exponent": exponent, "fraction": fraction}

# Accepts an IBM single-precision floating-point value and returns a Python
# float, or None on error.
def fromIBM(valueIBM):
    try:
        value = valueIBM["fraction"] / maxFraction1
        value *= exponentBase ** valueIBM["exponent"]
        if valueIBM["negative"]:
            value = -value
        return value
    except:
        return None

# Adds two IBM SD values.
def addIBM(value1, value2, subtract=False):
    negative1 = value1["negative"]
    exponent1 = value1["exponent"]
    fraction1 = value1["fraction"]
    negative2 = value2["negative"]
    exponent2 = value2["exponent"]
    fraction2 = value2["fraction"]
    if exponent1 > exponent2:
        shift = exponent1 - exponent2
        fraction2 = fraction2 >> (exponentBaseBits * shift)
        exponent2 += shift
    elif exponent2 > exponent1:
        shift = exponent2 - exponent1
        fraction1 = fraction1 >> (exponentBaseBits * shift)
        exponent1 += shift
    if negative1:
        fraction1 = -fraction1
    if negative2 != subtract:
        fraction2 = -fraction2
    exponent = exponent1
    fraction = fraction1 + fraction2
    negative = (fraction < 0)
    if negative:
        fraction = -fraction
    while fraction > maxFraction:
        fraction = fraction >> exponentBaseBits
        exponent += 1
    if fraction == 0:
        return zero
    while fraction < floorFraction:
        fraction = fraction << exponentBaseBits
        exponent -= 1
    if exponent < minExponent:
        return zero
    if exponent > maxExponent:
        return None
    return { "negative": negative, "exponent": exponent, "fraction": fraction}

# Multiplies two IBM SD values.
def multiplyIBM(value1, value2):
    negative1 = value1["negative"]
    exponent1 = value1["exponent"]
    fraction1 = value1["fraction"]
    negative2 = value2["negative"]
    exponent2 = value2["exponent"]
    fraction2 = value2["fraction"]
    if fraction1 == 0 or fraction2 == 0:
        return zero
    negative = (negative1 != negative2)
    exponent = exponent1 + exponent2
    fraction = fraction1 * fraction2 / maxFraction1
    while fraction > maxFraction:
        fraction /= exponentBase
        exponent += 1
    while fraction < floorFraction:
        fraction *= exponentBase
        exponent -= 1
    fraction = int(fraction + 0.5)
    if exponent < minExponent:
        return zero
    if exponent > maxExponent:
        return None
    return { "negative": negative, "exponent": exponent, "fraction": fraction}

def ibmToOctal(value, scale=26):
    if value == None:
        return None
    else:
        v = value["fraction"]
        shift = exponentBaseBits * value["exponent"] - scale + (25 - numMantissaBits) + 1
        if shift > 0:
            v = v << shift
        elif shift < 0:
            v = v >> (-shift)
        if v & 1:
            v += 1
        if value["negative"]:
            v = v ^ 0o777777776
        return "%09o" % v

# *** Note: --check is only partially implemented and not fully working. ***
# For checking values from assembly listings.  It reads an assembly listing
# and attempts to reproduce the assembler's computations, to the extent that
# its feeble parsing can manage.
if check:
    lines = sys.stdin.readlines()
    for line in lines:
        if len(line) < 70:
            continue
        if line[0] != " ":
            continue
        if line[38] != " " or line[48] != " ":
            continue
        constantString = line[39:48]
        isOctal = True
        for c in constantString:
            if c not in ["0", "1", "2", "3", "4", "5", "6","7"]:
                isOctal = False
                break
        if not isOctal:
            continue
        fields = line[58:].strip().split()
        if len(fields) < 2:
            continue
        operator = fields[0]
        if operator == "OCT":
            continue
        operand = fields[1]
        scale = 26
        fields = operand.split("B")
        if len(fields) > 1:
            scale = int(fields[1])
        value = fields[0]
        try:
            f = float(value)
            octal = ibmToOctal(toIBM(value))
            if octal == constantString:
                print("Match:   ", octal, line)
            else:
                print("Mismatch:", octal, line)
        except:
            pass

# For testing purposes:
if test:
    
    print("Numbers can include E-exponents but not B-scales.")
    print("B-scales can optionally appear at the end of a line.")
    print("Accepts line inputs in any of the following formats:")
    print("\tnumber [Bn]")
    print("\tnumber + number [Bn]")
    print("\tnumber - number [Bn]")
    print("\tnumber * number [Bn]")
    print("The default B-scale, affecting only outputs, is B26.")
    print("Notice the whitespaces delimiting operators and B-scales.")
    print("Also, in place of a number you can use the literal")
    print("string P.CSR, which is hard-coded as 4063.492.")
    print("Any of these line formats prints out the result of")
    print("the desired operation in 3 forms:  my representation")
    print("of an IBM single-precision floating-point number,")
    print("a normal fixed-point representation, and a 9-octal-digit")
    print("LVDC literal. The example, from p. 190 of AS-512, of")
    print("\t31440.2 * P.CSR B27")
    print("results in")
    print("\t{'negative': False, 'exponent': 7, 'fraction': 7984812}")
    print("\t127756992.0")
    print("\t363532540")
    while True:
        print("> ", end="")
        sys.stdout.flush()
        fields = sys.stdin.readline().strip().split()
        scale = 26
        if len(fields) > 1 and fields[-1][0] == "B":
            try:
                scale = int(fields[-1][1:])
                fields.pop()
            except:
                continue
        if len(fields) == 1:
            value = toIBM(fields[0])
            print(value)
            print(fromIBM(value))
            print(ibmToOctal(value, scale))
        elif len(fields) == 3:
            value1 = toIBM(fields[0])
            operator = fields[1]
            value2 = toIBM(fields[2])
            if operator == "+":
                value = addIBM(value1, value2)
            elif operator == "-":
                value = addIBM(value1, value2, True)
            elif operator == "*":
                value = multiplyIBM(value1, value2)
            else:
                continue
            print(value)
            print(fromIBM(value))
            print(ibmToOctal(value, scale))
            
back to top