/*
  CS 473, Fall 2002
  HW2 Sample solution

  Given two IEEE format floating point numbers input on the command
  line, this program will divide the first by the second, convert the
  result to a human-readable decimal format, and print the result to
  standard out.

*/

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef uint32_t Ieeefp;

/* constants defining fields. */
#define MASK(x) ((1 << (x))-1)

#define FRACWID 23
#define FRACMASK MASK(FRACWID)
#define PHANTOM (1 << FRACWID)
#define SIGNIFWID (FRACWID+1)

#define EXPWID  8
#define EXCESS  127
#define EXPMASK MASK(EXPWID)
#define EXPPOS  FRACWID

#define SIGNPOS (FRACWID + EXPWID)

#define DECFRACWID 28
#define DECFRACMSK MASK(DECFRACWID)

/* macroes for field manipulation. */
#define SIGN(x)  (((x) >> SIGNPOS) & 1)
#define EXPONENT(x) ((int)((((x) >> EXPPOS) & EXPMASK) - EXCESS))
#define SIGNIFICAND(x) (((x) & FRACMASK) | PHANTOM)

/* Forward declarations */
Ieeefp ieeediv(Ieeefp dividend, Ieeefp divisor);
void ieeeprint(Ieeefp value);
uint32_t integer(Ieeefp value);
uint32_t fraction(Ieeefp value);
uint32_t shift(uint32_t value, int shamt);
void printint(uint32_t intval);
void printfrac(uint32_t fracval);
     
/******************************************************************************
  main(int argc, char *argv[]);

  Purpose:  driver program.  Exercises division and output routines by
  doing a division and printing the result.

  Inputs:  two numbers in hexadecimal IEEE floating point format, on
  the command line.

  Return:  0 if given two inputs, 1 otherwise.  The *only* input
  checking performed is on the number of inputs.

  Output:  arg1/arg2, in human-readable decimal format.

******************************************************************************/
int main(int argc, char *argv[])
{
    Ieeefp dividend, divisor, result;

    /* Program spec didn't call for checking this (or anything else),
       but it'll keep me from making so many mistakes testing it */
    if (argc != 3) {
        fprintf(stderr, "usage:  ieeediv <dividend> <divisor>\n");
        return(1);
    }
    
    dividend = strtoul(argv[1], NULL, 16);
    divisor = strtoul(argv[2], NULL, 16);

    result = ieeediv(dividend, divisor);
    ieeeprint(result);

    return(0);
}

/******************************************************************************
  Ieeefp ieeediv(Ieeefp dividend, Ieeefp divisor);

  Purpose: Ieee division function

  Parameters:  two numbers in IEEE single-precision floating point format.

  Return:  arg1/arg2, in IEEE single-precision floating point format.

  Limitations:  can't handle denorms, infinities, or NaNs in either
  input or output.

  Algorithm note:  This simply performs the long division algorithm as
  presented in class.

******************************************************************************/
Ieeefp ieeediv(Ieeefp dividend, Ieeefp divisor)
{
    int signif1, signif2;
    int quotient_sign, quotient_exponent, quotient_significand;
    int i;

    /* Compute sign and initial exponent value */
    quotient_sign = SIGN(dividend) ^ SIGN(divisor);
    quotient_exponent = EXPONENT(dividend) - EXPONENT(divisor);

    /* Compute significand using long division */
    signif1 = SIGNIFICAND(dividend);
    signif2 = SIGNIFICAND(divisor);
    quotient_significand = 0;
    for (i = 0; i < SIGNIFWID; i++) {
        quotient_significand <<= 1;
        if (signif1 >= signif2) {
            quotient_significand |= 1;
            signif1 -= signif2;
        }
        signif2 >>= 1;
    }

    /* Renormalize */
    if (!(quotient_significand & PHANTOM)) {
        quotient_significand <<= 1;
        quotient_exponent -= 1;
    }

    return (quotient_sign << SIGNPOS) |
           ((quotient_exponent + EXCESS) << EXPPOS) |
           (quotient_significand & ~PHANTOM);
}

/******************************************************************************
  void ieeeprint(Ieeefp value)

  Purpose:  IEEE floating point number output

  Parameter:  One number in IEEE floating point format

  Output: Number printed to stdout in human-readable decimal format,
          i.e. [-][0-9]*\.[0-9]*.  No leading or trailing 0's.

  Limitations:  Can only handle numbers with an integer part that fits
  in 32 bits, and a fraction part that fits in 28 bits.  Also, no
  denorms (including 0), infinities, or NaNs
  
******************************************************************************/
void ieeeprint(Ieeefp value)
{
    if (SIGN(value) == 1)
        putchar('-');

    printint(integer(value));

    putchar('.');

    printfrac(fraction(value));

    putchar('\n');
}

/*****************************************************************************
uint32_t integer(Ieeefp value)

Purpose:  extract integer part of number represented in IEEE format

Parameter:  Number in IEEE format (representing, for instance, 27.125)

Result:  Integer part of number (in this instance, 27)

Note:  It does it bys hifting the input so that the 2**0 place is in
the right-most bit.  Taking a series of numbers of the form 2**i, we
can work out what the shifts have to be:

  i  value direction amt
  0    0x00000001     right    23
  1    0x00000001     right    22
 23    0x00800000     -----     0
 24    0x01000000     left      1
******************************************************************************/
uint32_t integer(Ieeefp value)
{
    return shift(SIGNIFICAND(value), EXPONENT(value) - FRACWID);
}

/*****************************************************************************
uint32_t fraction(Ieeefp value)

Purpose:  extract fraction part of number represented in IEEE format

Parameter:  Number in IEEE format (representing, for  instance, 27.125)

Result:  Fraction part of number, shifted so the binary point is four
bits from the leftmost bit of the word.  In this instance, it would be
.125 appropriately shifted, which  would be 0x01000000

Note:  It does it by shifting to the right place, and masking off the
integer part (most significant four bits).  As with integers, we can
work out what the shifts have to be by compilining a table for numbers
of the form 1.5*2**i:

  i  value       direction amt
   0 0x08000000   left      4
  -1 0x04000000   left      3
  -2 0x02000000   left      2
  -3 0x01000000   left      1
  -4 0x00800000   ----      0
  -5 0x00400000   right     1

******************************************************************************/
uint32_t fraction(Ieeefp value)
{
    return shift(SIGNIFICAND(value), DECFRACWID - FRACWID + EXPONENT(value)) & DECFRACMSK;
}

/*****************************************************************************
uint32_t shift(value, shamt)

Purpose:  perform an arbitrary shift.  Positive is left, negative is right.
Checks for shifts greater than 31 in either direction, and zeroes
result

Parameters:  value to be shifted, amount to be shifted by

Returns:     shifted value

******************************************************************************/
uint32_t shift(uint32_t value, int shamt)
{
    if (shamt < 0)
        if (shamt < -31)
            return 0;
        else
            return value >> -shamt;
    else if (shamt > 0)
        if (shamt > 31)
            return 0;
        else
            return value << shamt;
    else return value;
}
    
/******************************************************************************
void printint(uint32_t intval)

Purpose:  Print an integer, in decimal.

Parameter:  value to be printed

Output:  value, in decimal

******************************************************************************/
void printint(uint32_t intval)
{
    if (intval != 0) {
        printint(intval/10);
        putchar((intval % 10) + '0');
    }
}

/******************************************************************************
void printfrac(uint32_t fracval)

Purpose:  Print a fraction, in decimal

Parameter:  value to be printed

Output:  value, in decimal

******************************************************************************/
void printfrac(uint32_t fracval)
{
    while ((fracval & DECFRACMSK) != 0) {
        fracval = (fracval & DECFRACMSK)* 10;
        putchar((fracval >> DECFRACWID) + '0');
    }
}

