/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#define	BLC_CTYPE_INLINE
#include <ctype.h>
#include <misc/multiply.h>
#include <blc_float.h>
#include <blc_stdlib.h>
#include <libm.h>

/****************************************************************************/
/*					Local definitions										*/
/****************************************************************************/

/*
*	Internal flags. Note that the topmost bit must not be used, as
*	the negative flag value is used to indicate under- or overflow.
*/

#define	FLAG_MSIG	0x40			// Mantissa signum was seen
#define	FLAG_MNEG	0x20			// Negative number
#define	FLAG_ESIG	0x10			// Exponent signum was seen
#define	FLAG_ENEG	0x08			// Exponent is negative
#define	FLAG_HEXA	0x04			// The number is hexadecimal
#define	FLAG_SEEN	0x02			// At least one digit was seen
#define	FLAG_NONZ	0x01			// At least one non-zero digit was seen

/****************************************************************************/
/*					Local prototypes										*/
/****************************************************************************/

static	int		StateSpace( BLC_S2F *stat, int next );
static	int		StateFirst( BLC_S2F *stat, int next );
static	int		StateIshex( BLC_S2F *stat, int next );
static	int		StateWhole( BLC_S2F *stat, int next );
static	int		StateFract( BLC_S2F *stat, int next );
static	int		StateInt16( BLC_S2F *stat, int next );
static	int		StateFra16( BLC_S2F *stat, int next );
static	int		StateExpon( BLC_S2F *stat, int next );
static	int		StateDoInf( BLC_S2F *stat, int next );
static	int		StateDoNaN( BLC_S2F *stat, int next );
static	void	AddDecChar( BLC_S2F *stat, int next );
static	void	AddHexChar( BLC_S2F *stat, int next );
static	void	Conversion( BLC_S2F *stat );
static	void	BuildFloat( BLC_S2F *stat, unsigned long long mant, int bexp );


/****************************************************************************/
/*					Local constants											*/
/****************************************************************************/

static const MULT	neg[] =	{

	{ { 0xcccccccc, 0xcccccccc, 0xcccccccc },  -1,  -3 },
	{ { 0xa3d70a3d, 0x70a3d70a, 0x3d70a3d7 },  -2,  -6 },
	{ { 0x83126e97, 0x8d4fdf3b, 0x645a1cac },  -3,  -9 },
	{ { 0xd1b71758, 0xe219652b, 0xd3c36113 },  -4, -13 },
	{ { 0xa7c5ac47, 0x1b478423, 0x0fcf80dc },  -5, -16 },
	{ { 0x8637bd05, 0xaf6c69b5, 0xa63f9a49 },  -6, -19 },
	{ { 0xd6bf94d5, 0xe57a42bc, 0x3d329076 },  -7, -23 },
	{ { 0xabcc7711, 0x8461cefc, 0xfdc20d2b },  -8, -26 },
	{ { 0x89705f41, 0x36b4a597, 0x31680a88 },  -9, -29 },
	{ { 0xdbe6fece, 0xbdedd5be, 0xb573440e }, -10, -33 },
	{ { 0xafebff0b, 0xcb24aafe, 0xf78f69a5 }, -11, -36 },
	{ { 0x8cbccc09, 0x6f5088cb, 0xf93f87b7 }, -12, -39 },
	{ { 0xe12e1342, 0x4bb40e13, 0x2865a5f2 }, -13, -43 },
	{ { 0xb424dc35, 0x095cd80f, 0x538484c1 }, -14, -46 },
	{ { 0x901d7cf7, 0x3ab0acd9, 0x0f9d3701 }, -15, -49 },
	{ { 0xe69594be, 0xc44de15b, 0x4c2ebe68 }, -16, -53 },
	{ { 0xb877aa32, 0x36a4b449, 0x09befeb9 }, -17, -56 },
	{ { 0x9392ee8e, 0x921d5d07, 0x3aff322e }, -18, -59 }
};

static const MULT	pos[] =	{

	{ { 0xa0000000, 0x00000000, 0x00000000 },  +1,  +4 },
	{ { 0xc8000000, 0x00000000, 0x00000000 },  +2,  +7 },
	{ { 0xfa000000, 0x00000000, 0x00000000 },  +3, +10 },
	{ { 0x9c400000, 0x00000000, 0x00000000 },  +4, +14 },
	{ { 0xc3500000, 0x00000000, 0x00000000 },  +5, +17 },
	{ { 0xf4240000, 0x00000000, 0x00000000 },  +6, +20 },
	{ { 0x98968000, 0x00000000, 0x00000000 },  +7, +24 },
	{ { 0xbebc2000, 0x00000000, 0x00000000 },  +8, +27 },
	{ { 0xee6b2800, 0x00000000, 0x00000000 },  +9, +30 },
	{ { 0x9502f900, 0x00000000, 0x00000000 }, +10, +34 },
	{ { 0xba43b740, 0x00000000, 0x00000000 }, +11, +37 },
	{ { 0xe8d4a510, 0x00000000, 0x00000000 }, +12, +40 },
	{ { 0x9184e72a, 0x00000000, 0x00000000 }, +13, +44 },
	{ { 0xb5e620f4, 0x80000000, 0x00000000 }, +14, +47 },
	{ { 0xe35fa931, 0xa0000000, 0x00000000 }, +15, +50 },
	{ { 0x8e1bc9bf, 0x04000000, 0x00000000 }, +16, +54 },
	{ { 0xb1a2bc2e, 0xc5000000, 0x00000000 }, +17, +57 },
	{ { 0xde0b6b3a, 0x76400000, 0x00000000 }, +18, +60 }
};

/****************************************************************************/
/*					Interface functions										*/
/****************************************************************************/

void	_blc_StrToFlt( BLC_S2F *stat )
{
	stat->func = StateSpace;
	stat->expo = 0;
	stat->last = 0;
	stat->flag = 0;
	stat->data.accu[ 0 ] = 0;
	stat->data.accu[ 1 ] = 0;
	stat->data.accu[ 2 ] = 0;
}

/****************************************************************************/
/*					Conversion state machine states							*/
/****************************************************************************/

/*
*	Skip leading spaces and process the signum
*/

static	int		StateSpace( BLC_S2F *stat, int next )
{
	if ( isspace( next ) ) return( 0 );

	stat->func = StateFirst;

	if ( next == '+' ) {

		stat->flag = FLAG_MSIG;
		return( 0 );
	}

	if ( next == '-' ) {

		stat->flag = FLAG_MSIG | FLAG_MNEG;
		return( 0 );
	}

	return( StateFirst( stat, next ) );
}

/*
*	Check the first character after the signum
*/

static	int		StateFirst( BLC_S2F *stat, int next )
{
	if ( next == '0' ) {

		stat->func = StateIshex;
		return( 0 );
	}
	else {

		stat->func = StateWhole;
		return( StateWhole( stat, next ) );
	}
}

/*
*	Check if we have a hexadecimal number
*/

static	int		StateIshex( BLC_S2F *stat, int next )
{
	if ( stat->flag & FLAG_HEXA ) {

		if ( ! isxdigit( next ) ) {

			Conversion( stat );
			return( +2 );
		}
		else {

			stat->flag |= FLAG_HEXA;
			stat->func  = StateInt16;
			return( StateInt16( stat, next ) );
		}
	}
	else {

		if ( next == 'x' || next == 'X' ) {

			// We might have a hex format number

			stat->flag |= FLAG_HEXA;
			return( 0 );
		}
		else {

			stat->func = StateWhole;
			StateWhole( stat, '0' );
			return( StateWhole( stat, next ) );
		}
	}
}

/*
*	Process the integer bits of a decimal mantissa
*/

static	int		StateWhole( BLC_S2F *stat, int next )
{
	if ( ! ( stat->flag & FLAG_SEEN ) ) {

		if ( toupper( next ) == 'I' ) {

			stat->func = StateDoInf;
			return( 0 );
		}

		if ( toupper( next ) == 'N' ) {

			stat->func = StateDoNaN;
			return( 0 );
		}
	}

	if ( next == '.' ) {

		stat->func = StateFract;
		return( 0 );
	}

	if ( next == 'e' || next == 'E' ) {

		if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
		stat->last += stat->expo;
		stat->expo  = 0;
		stat->func  = StateExpon;
		return( 0 );
	}

	if ( isdigit( next ) ) {

		stat->flag |= FLAG_SEEN;

		if ( ! ( stat->flag & FLAG_NONZ ) ) {

			if ( next == '0' )

				return( 0 );
			else
				stat->flag |= FLAG_NONZ;
		}

		AddDecChar( stat, next );
		return( 0 );
	}

	if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
	Conversion( stat );
	return( +1 );
}

/*
*	Process the fractional digits of a decimal mantissa
*/

static	int		StateFract( BLC_S2F *stat, int next )
{
	if ( next == 'e' || next == 'E' ) {

		if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
		stat->last += stat->expo;
		stat->expo  = 0;
		stat->func  = StateExpon;
		return( 0 );
	}

	if ( isdigit( next ) ) {

		stat->flag |= FLAG_SEEN;

		if ( ! ( stat->flag & FLAG_NONZ ) ) {

			if ( next == '0' ) {

				stat->last--;
				return( 0 );
			}
			else
				stat->flag |= FLAG_NONZ;
		}

		AddDecChar( stat, next );
		stat->last--;
		return( 0 );
	}

	if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
	Conversion( stat );
	return( +1 );
}

/*
*	Process the integer bits of a hexadecimal mantissa
*/

static	int		StateInt16( BLC_S2F *stat, int next )
{
	if ( next == '.' ) {

		stat->func = StateFra16;
		return( 0 );
	}

	if ( next == 'p' || next == 'P' ) {

		if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
		stat->last = stat->expo;
		stat->expo = 0;
		stat->func = StateExpon;
		return( 0 );
	}

	if ( isxdigit( next ) ) {

		stat->flag |= FLAG_SEEN;

		if ( ! ( stat->flag & FLAG_NONZ ) ) {

			if ( next == '0' )

				return( 0 );
			else
				stat->flag |= FLAG_NONZ;
		}

		AddHexChar( stat, next );
		return( 0 );
	}

	if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
	stat->last = stat->expo;
	stat->expo = 0;
	Conversion( stat );
	return( +1 );
}

/*
*	Process the fractional digits of a hexadecimal mantissa
*/

static	int		StateFra16( BLC_S2F *stat, int next )
{
	if ( next == 'p' || next == 'P' ) {

		if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
		stat->last += stat->expo;
		stat->expo  = 0;
		stat->func  = StateExpon;
		return( 0 );
	}

	if ( isxdigit( next ) ) {

		stat->flag |= FLAG_SEEN;

		if ( ! ( stat->flag & FLAG_NONZ ) ) {

			if ( next == '0' ) {

				stat->last -= 4;
				return( 0 );
			}
			else
				stat->flag |= FLAG_NONZ;
		}

		AddHexChar( stat, next );
		stat->last -=4;
		return( 0 );
	}

	if ( ! ( stat->flag & FLAG_SEEN ) ) return( -1 );
	Conversion( stat );
	return( +1 );
}

/*
*	Process the exponent, common for decimal and hex case
*/

static	int		StateExpon( BLC_S2F *stat, int next )
{
	if ( ! ( stat->flag & FLAG_ESIG ) ) {

		if ( next == '+' ) {

			stat->flag |= FLAG_ESIG;
			return( 0 );
		}

		if ( next == '-' ) {

			stat->flag |= FLAG_ESIG | FLAG_ENEG;
			return( 0 );
		}

		if ( isdigit( next ) ) stat->flag |= FLAG_ESIG;
	}

	if ( isdigit( next ) ) {

		if ( stat->expo < 200000000 ) {

			stat->expo *= 10;
			stat->expo += next - '0';
		}

		return( 0 );
	}

	Conversion( stat );

	if ( ! ( stat->flag & FLAG_ESIG ) )

		return( 2 );
	else
		return( 1 );
}

/*
*	Process infinity
*/

static	int		StateDoInf( BLC_S2F *stat, int next )
{
	if ( next && toupper( next ) == stat->last++ ["NFINITY"] ) return( 0 );
	if ( stat->last < 3 ) return( -1 );
	BuildFloat( stat, 0, 2047 );
	return( +1 );
}

/*
*	Process NaN
*/

static	int		StateDoNaN( BLC_S2F *stat, int next )
{
	if ( next && toupper( next ) == stat->last++ ["AN"] ) return( 0 );
	if ( stat->last < 3 ) return( -1 );
	stat->flag = 0;
	BuildFloat( stat, 1ull << 51, 2047 );
	return( +1 );
}

/****************************************************************************/
/*					Other local functions									*/
/****************************************************************************/

/*
*	Processes the next decimal digit
*/

static	void	AddDecChar( BLC_S2F *stat, int next )
{
unsigned long long	x;

	if ( ( stat->data.accu[ 0 ] >> 29 ) || next == '0' ) {

		stat->expo++;
	}
	else {

		while ( stat->expo && ! ( stat->data.accu[ 0 ] >> 29 ) ) {

			x = umul32x32_64( stat->data.accu[ 2 ], 10 );
			stat->data.accu[ 2 ] = x;
			x = umul32x32_64( stat->data.accu[ 1 ], 10 ) + ( x >> 32 );
			stat->data.accu[ 1 ] = x;
			stat->data.accu[ 0 ] = stat->data.accu[ 0 ] * 10 + ( x >> 32 );
			stat->expo--;
		}

		if ( ! ( stat->data.accu[ 0 ] >> 29 ) ) {

			x = umul32x32_64( stat->data.accu[ 2 ], 10 ) + ( next - '0' );
			stat->data.accu[ 2 ] = x;
			x = umul32x32_64( stat->data.accu[ 1 ], 10 ) + ( x >> 32 );
			stat->data.accu[ 1 ] = x;
			stat->data.accu[ 0 ] = stat->data.accu[ 0 ] * 10 + ( x >> 32 );
		}
		else
			stat->expo++;
	}
}

/*
*	Processes the next hexadecimel digit
*/

static	void	AddHexChar( BLC_S2F *stat, int next )
{
	if ( stat->data.accu[ 0 ] >> 28 ) {

		stat->expo += 4;
	}
	else {

		stat->data.accu[ 0 ]  = stat->data.accu[ 0 ] << 4;
		stat->data.accu[ 0 ] |= stat->data.accu[ 1 ] >> 28;
		stat->data.accu[ 1 ]  = stat->data.accu[ 1 ] << 4;
		stat->data.accu[ 1 ] |= stat->data.accu[ 2 ] >> 28;
		stat->data.accu[ 2 ]  = stat->data.accu[ 2 ] << 4;
		stat->data.accu[ 2 ] |= next > '9' ? toupper( next ) - '7' : next - '0';
	}
}

/*
*	Calculates the final result
*/

static	void	Conversion( BLC_S2F *stat )
{
unsigned long long	mant;
int					bexp;
int					dexp;
const MULT	*ptr;
int k;

	if ( ! ( stat->flag & FLAG_NONZ ) ) {

		// The result is 0.

		BuildFloat( stat, 0, 0 );
		return;
	}

	bexp = 0;

	// We have to normalise the number

	while ( ! stat->data.accu[ 0 ] ) {

		stat->data.accu[ 0 ] = stat->data.accu[ 1 ];
		stat->data.accu[ 1 ] = stat->data.accu[ 2 ];
		stat->data.accu[ 2 ] = 0;
		bexp += 32;
	}

	for ( k = 0 ; ! ( stat->data.accu[ 0 ] >> 31 ) ; k++ ) stat->data.accu[ 0 ] <<= 1;

	if ( k ) {

		stat->data.accu[ 0 ] |=  stat->data.accu[ 1 ] >> ( 32 - k );
		stat->data.accu[ 1 ] <<= k;
		stat->data.accu[ 1 ] |=  stat->data.accu[ 2 ] >> ( 32 - k );
		stat->data.accu[ 2 ] <<= k;
	}

	bexp = 95 - bexp - k;

	if ( stat->flag & FLAG_HEXA ) {

		if ( stat->flag & FLAG_ENEG )

			bexp += stat->last - stat->expo;
		else
			bexp += stat->last + stat->expo;
	}
	else {

		if ( stat->flag & FLAG_ENEG )

			dexp = stat->last - stat->expo;
		else
			dexp = stat->last + stat->expo;

		if ( dexp < -326 ) {

			// Result is underflow

			stat->flag = -1;
			BuildFloat( stat, 1, 0 );
			return;
		}

		if ( dexp > 309 ) {

			// Result is overflow, return max

			stat->flag = -1;
			BuildFloat( stat, ( 1ull << 52 ) - 1, 2046 );
			return;
		}

		for ( ptr = _blc_float_neg ; dexp <= -19 ; ptr++ ) {

			if ( dexp - ptr->decexp <= 0 ) {

				bexp -= _blc_float_umul96x96( stat->data.accu, ptr->mant96 );
				bexp += ptr->binexp + 1;
				dexp -= ptr->decexp;
				ptr--;
			}
		}

		for ( ptr = _blc_float_pos ; dexp >= +19 ; ptr++ ) {

			if ( dexp - ptr->decexp >= 0 ) {

				bexp -= _blc_float_umul96x96( stat->data.accu, ptr->mant96 );
				bexp += ptr->binexp + 1;
				dexp -= ptr->decexp;
				ptr--;
			}
		}

		// Now the decimal exponent is +18 .. -18 so we need at most one
		// more multiplication to get it into its place.

		if ( dexp < 0 ) {

	   		ptr = neg - 1 - dexp;
			bexp -= _blc_float_umul96x96( stat->data.accu, ptr->mant96 );
			bexp += ptr->binexp;
		}

		if ( dexp > 0 ) {

			ptr = pos - 1 + dexp;
			bexp -= _blc_float_umul96x96( stat->data.accu, ptr->mant96 );
			bexp += ptr->binexp;
		}
	}

	// Round the result to 53 bit
	// We will have 53 valid bits, i.e. we'll throw away 11 bits.
	// That is, bit 11 is L, 10 is G, 9 is R and 8 should be S.

	mant  = (unsigned long long) stat->data.accu[ 0 ] << 21;
	mant |= (unsigned long long) stat->data.accu[ 1 ] >> 11;

	if ( stat->data.accu[ 1 ] & 0x400 ) {

		if ( ( mant & 1 ) ||
			 ( stat->data.accu[ 1 ] & 0x3ff ) ||
			 stat->data.accu[ 2 ] ) {

			mant++;

			if ( mant & ( 1ull << 53 ) ) {

				mant >>= 1;
				bexp++;
			}
		}
	}

	// Offset the binary exponent

	bexp += 1023;

	// Check if the binary exponent is in range

	if ( bexp > 2046 ) {

		// Overflow, return infinite

		stat->flag = -1;
		BuildFloat( stat, ( 1ull << 52 ) - 1 , 2046 );
		return;
	}

	if ( bexp < -51 ) {

		// Underflow

		stat->flag = -1;
		BuildFloat( stat, 1, 0 );
		return;
	}

	if ( bexp <= 0 ) {

		// Unnormalised number, build it

		mant >>= 1 - bexp;
		bexp = 0;
	}
	else {

		// Normalised number, get rid of the leading '1'

		mant &= ~ ( 1ull << 52 );
	}

	// Now we can build the whole result

	BuildFloat( stat, mant, bexp );
}

static	void	BuildFloat( BLC_S2F *stat, unsigned long long mant, int bexp )
{
	mant |= (unsigned long long) bexp << 52;
	mant |= ( stat->flag & FLAG_MNEG ) ? 1ull << 63 : 0;
	stat->data.numb = _blm_ulltod( mant );
}

