
# floatUtils.py

import decimal, sys
from decimal import Decimal as D
import struct


baseStr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def toBase(n, base):
  res = ""
  while n:
    res += baseStr[n % base]
    n //= base
  return res[::-1] or "0"


def f2b(num):
  # pack the float as a binary string
  s = struct.pack('!d', num) 
  # convert each byte to binary and join them
  return ''.join(format(c, '0>8b') for c in s)


def b2f(b):
    # unpack the binary string as a float
    return struct.unpack('!d', int(b, 2).to_bytes(8, byteorder='big'))[0]


def printIEEE754(num): # (64 bit)
  bi = f2b(num)
  sign = "+" if ((bi[0]) == "0") else "-"  # sign, 1 bit
  exp = int(bi[1:12],2) - ((2**10)-1)    # exponent, 11 bits

  print(f"\nIEEE754 of {num} = \n{bi[0]} : {bi[1:12]} : {bi[12:]}")

  mantissa = int(bi[12:],2)
  signVal = 1 if sign == "+" else -1
  if num >= sys.float_info.min:  # a normal number
    normMant = 1 + mantissa/(1 << 52)   # mantissa: 52 bits
    print(f"  Sign == {sign}; Exponent == {exp}; mantissa == {normMant}")
    print("  Decimal value:", D(signVal) * D(normMant) * D(2)**D(exp))
  else:  # subnormal
    subnormMant = mantissa/(1 << 52)
    print(f"Subnormal:  Sign == {sign}; Exponent == {-1022}; mantissa == {subnormMant}")
    print("             Dec value:", D(signVal) * D(subnormMant) * D(2)**D(-1022))


def f2d(val, prec = 15):
  # convert a float to a decimal with prec dps
  return D(val).quantize( D(10)**-prec)


def addBinary(aStr, bStr):
  tot = bin(int(aStr, 2) + int(bStr, 2))
  return tot[2:]   # strip off 0b


# -------------------------------------

if __name__ == "__main__":

  print()
  print("3 in base two =", toBase(3, 2))
  print("Binary version of 3 =\n", f2b(3))
  print("Fb2f(f2b(3)) =", b2f(f2b(3)))
  print("b2f(f2b(3)) == 3?", b2f(f2b(3)) == 3)
  printIEEE754(3)

  print()
  print("Binary version of 0.3 =\n", f2b(0.3))
  print("b2f(f2b(0.3) =", b2f(f2b(0.3)))
  print("b2f(f2b(0.3)) == 0.3?", b2f(f2b(0.3)) == 0.3)
  printIEEE754(0.3)

  print()
  print("b2f(f2b(0.1)) =", b2f(f2b(0.1)))
  print("b2f(f2b(0.1)) == 0.1?", b2f(f2b(0.1)) == 0.1)
  printIEEE754(0.1)


  print()
  print("0.1 + 0.2 == 0.3", 0.1 + 0.2 == 0.3)
  print("0.1 + 0.2 == 0.3", f2d(0.1) + f2d(0.2) == f2d(0.3))
  
  for x in [0.1, 0.2, 0.3, f2d(0.1), f2d(0.2), f2d(0.3)]:
    print(f"{type(x).__name__:8} :  {x:.18f}")