/*++

Copyright (c) 1995-1998 Microsoft Corporation

Module Name:

    fpubcd.c

Abstract:

    Floating point BCD fragments (FBLD, FBSTP)

Author:

    05-Oct-1995 BarryBo

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <float.h>
#include <math.h>
#include <errno.h>
#include <stdio.h>
#include "wx86.h"
#include "fragp.h"
#include "fpufrags.h"
#include "fpufragp.h"

typedef VOID (*NpxPutBCD)(PCPUDATA cpu, PFPREG Fp, PBYTE pop1);
#define NPXPUTBCD(name) VOID name(PCPUDATA cpu, PFPREG Fp, PBYTE pop1)

NPXPUTBCD(FBSTP_VALID);
NPXPUTBCD(FBSTP_ZERO);
NPXPUTBCD(FBSTP_SPECIAL);
NPXPUTBCD(FBSTP_EMPTY);

const NpxPutBCD FBSTPTable[TAG_MAX] = {
    FBSTP_VALID,
    FBSTP_ZERO,
    FBSTP_SPECIAL,
    FBSTP_EMPTY
};

const double BCDMax=999999999999999999.0;

VOID
StoreIndefiniteBCD(
    PBYTE pop1
    )
/*++

Routine Description:

    Write out the BCD encoding for INDEFINITE.

    Note that ntos\dll\i386\emlsbcd.asm writes out a different
    bit-pattern than the 487 does!  The value written here matches
    a Pentium's response.

Arguments:

    pop1 - address of BCD to write to

Return Value:

    None

--*/
{
    //
    // Write out:          0xffff c0000000 00000000
    // emlsbcd.asm writes: 0xffff 00000000 00000000
    //                            ^
    //
    PUT_LONG(pop1, 0);
    PUT_LONG(pop1+4, 0xc0000000);
    PUT_SHORT(pop1+8, 0xffff);
}



FRAG1(FBLD, BYTE)
{
    FpArithDataPreamble(cpu, pop1);

    cpu->FpStatusC1 = 0;        // assume no error
    if (cpu->FpStack[ST(7)].Tag != TAG_EMPTY) {
        HandleStackFull(cpu, &cpu->FpStack[ST(7)]);
    } else {
        LONGLONG I64;
        DWORD dw0;
        INT Bytes;
        BYTE Val;
        PFPREG ST0;

        //
        // Get the BCD value into the FPU
        //
        dw0 = GET_LONG(pop1);

        PUSHFLT(ST0);

        if (dw0 == 0) {
            DWORD dw1 = GET_LONG(pop1+4);
            USHORT us0 = GET_SHORT(pop1+8);

            if (dw1 == 0xc0000000 && us0 == 0xffff) {

                //
                // The value is INDEFINITE
                //
                SetIndefinite(ST0);
                return;

            } else if (dw1 == 0 && (us0 & 0xff) == 0) {

                //
                // The value is +/- 0
                //
                ST0->Tag = TAG_ZERO;
                ST0->r64 = 0;
                ST0->rb[7] = (us0 >> 8); // copy in the sign bit
                return;
            }
        }

        //
        // Otherwise, the BCD value is TAG_VALID - load the digits in
        //
        I64 = 0;
        for (Bytes=8; Bytes>=0; --Bytes) {
            Val = GET_BYTE(pop1+Bytes);
            I64 = I64*100 + (Val>>4)*10 + (Val&0x0f);
        }

        //
        // Get the sign bit
        //
        Val = GET_BYTE(pop1+9) & 0x80;

        //
        // Set up the FP reg
        //
        ST0->Tag = TAG_VALID;
        ST0->r64 = (double)I64;
        ST0->rb[7] |= Val;       // copy in the sign bit
    }
}

NPXPUTBCD(FBSTP_VALID)
{
    BYTE Sign = Fp->rb[7] & 0x80;       // preserve the R8 sign
    BYTE Val;
    INT Bytes;
    LONGLONG I64;
    LONGLONG NewI64;
    DOUBLE r64;

    //
    // Take the absolute value of the R8 by clearing its sign bit
    //
    r64 = Fp->r64;
    *((PBYTE)&r64+7) &= 0x7f;

    //
    // Check the range of the R8
    //
    if (r64 > BCDMax) {
        //
        // Overflow - write out BCD indefinite.
        //
        StoreIndefiniteBCD(pop1);
        return;
    }

    //
    // Convert to an integer according the the current rounding mode
    //
    I64 = (LONGLONG)r64;

    //
    // Convert the integer to BCD, two digits at a time, and store it
    //
    for (Bytes = 0; Bytes < 9; ++Bytes) {
        NewI64 = I64 / 10;
        Val = (BYTE)(I64 - NewI64*10);  // low nibble Val = I64 mod 10
                                        // high nibble Val = 0

        I64 = NewI64 / 10;
        Val += 16*(BYTE)(NewI64 - I64*10);    // low nibble Val = I64 mod 10
                                        // high nibble Val = (I64/10) mod 10

        //
        // Store the two BCD digits
        //
        PUT_BYTE(pop1, Val);

        //
        // I64 has been divided by 100 since the top of the loop, so
        // there is nothing to do to it in order to loop again.  Update
        // the address we are writing to, then loop.
        //
        pop1++;
    }

    //
    // Store the sign bit, along with 7 zero bits in the top byte
    //
    PUT_BYTE(pop1, Sign);
    POPFLT;
}

NPXPUTBCD(FBSTP_ZERO)
{
    // Store out the signed zero value
    memset(pop1, 0, 9);
    PUT_BYTE(pop1+9, Fp->rb[7]);
    POPFLT;
}

NPXPUTBCD(FBSTP_SPECIAL)
{
    if (Fp->TagSpecial) {
        FBSTP_VALID(cpu, Fp, pop1);
    } else {
        //
        // INFINITY and NANs are invalid, and the masked behavior is
        // to write out INDEFINITE.
        //
        if (!HandleInvalidOp(cpu)) {
            StoreIndefiniteBCD(pop1);
            POPFLT;
        }
    }
}

NPXPUTBCD(FBSTP_EMPTY)
{
    if (!HandleStackEmpty(cpu, Fp)) {
        StoreIndefiniteBCD(pop1);
    }
}

FRAG1(FBSTP, BYTE)
{
    PFPREG ST0;

    FpArithDataPreamble(cpu, pop1);
    ST0 = cpu->FpST0;
    (*FBSTPTable[ST0->Tag])(cpu, ST0, pop1);
}