lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Since Lua is C89, here's a function dbltohex that converts a double to a string just like the "%.13a" C99 specifier would do, and hextodbl to convert the string back to a double. Care has been taken to make the code C89 and it compiles with gcc -ansi. The code is on public domain.

I don't claim that those functions are 100% correct, or as fast and/or concise as they could be. Particularly, infinities are converted to "Infinity" and "-Infinity" (which is different from gcc's output) and NaNs are converted to "NaN" (which is also different from gcc's output), and "NaN" is always converted to the quiet NaN 0x7fffffffffffffff. In other words, NaNs are lost during double -> ASCII -> double conversion. Error checking is absent, and hextodbl assumes the input string was generated by dbltohex.

The code assumes the host uses ASCII characters, and uses macros from limits.h to define uint as an unsigned integer with 32 bits. If it doesn't find a suitable type, you'll have to define uint manually. stdint.h is C99 :(

There's also code to check for little/big endian but the code was tested only on a little endian machine (Intel 64 bits with a 32-bits MinGW compiler.)

Cheers,

Andre
/* For sprintf */
#include <stdio.h>
/* For strcpy */
#include <string.h>
/* For atoi */
#include <stdlib.h>
/* For limit macros */
#include <limits.h>

#if UINT_MAX == 0xffffffffUL
#define uint unsigned int
#elif ULONG_MAX == 0xffffffffUL
#define uint unsigned long
#elif ULLONG_MAX == 0xffffffffUL
#define uint unsigned long long
#else
#error Please define uint as an unsigned integer with exactly 32 bits.
#endif

/*
Define an union to make it easy to access the bits of a double.
*/
typedef union
{
	double d;
	struct
	{
		uint w0;
		uint w1;
	} w;
} du_t;

/*
Writes d as an hexadecimal double to buffer. The maximum number of characters
written to buffer is 1 for the negative sign, 2 for the 0x prefix, 1 for the
integer digit, 1 for the dot, 12 for the fraction digits, 1 for the 'p'
indicating the exponent, 4 for the largest exponent and 1 for the terminating
null character, summing 23 characters. The result is always equal to the
printf %.13a specifier (C99), except for infinities and nans.
*/
static void dbltohex( char* buffer, double d )
{
	du_t u;
	int  negative, exponent, zeroedm, i;
	
	/* Put the double into the union. */
	u.d = d;
	
	/* Swap u.w.w0 and u.w.w1 if the endian is big. We should use a macro to */
	/* make the du_t union correct on both big and little endian machines... */
	i = 1;
	if ( *(char*)&i == 0 )
	{
		uint temp = u.w.w0;
		u.w.w0 = u.w.w1;
		u.w.w1 = temp;
	}
	
	/* Negative is true if the double is negative. */
	negative = ( u.w.w1 & 0x80000000UL ) != 0;
	
	/* The biased exponent. */
	exponent = ( u.w.w1 >> 20 ) & 0x7ff;
	
	/* Zeroedm is true if the mantissa is zero. */
	zeroedm  = ( u.w.w1 & 0x000fffff ) == 0 && u.w.w0 == 0;
	
	/* If the exponent is 0x7ff, it's an infinity or a nan. */
	if ( exponent == 0x7ff )
	{
		/* If the mantissa is zero, it's an infinity. */
		if ( zeroedm )
		{
			if ( negative )
			{
				*buffer++ = '-';
			}
			strcpy( buffer, "Infinity" );
		}
		/* Otherwise, it's a nan. */
		else
		{
			strcpy( buffer, "NaN" );
		}
		return;
	}
	
	/* Write the sign. */
	if ( negative )
	{
		*buffer++ = '-';
	}
	
	/* Write the 0x prefix. */
	*buffer++ = '0';
	*buffer++ = 'x';
	
	/* The first digit is 1 for normals or 0 for denormals. */
	if ( exponent == 0 )
	{
		*buffer++ = '0';
	}
	else
	{
		*buffer++ = '1';
	}
	
	/* Write the dot. */
	*buffer++ = '.';
	
	/* Write the mantissa. */
	for ( i = 16; i >= 0; i -= 4 )
	{
		*buffer++ = "0123456789abcdef"[ ( u.w.w1 >> i ) & 0xf ];
	}
	for ( i = 28; i >= 0; i -= 4 )
	{
		*buffer++ = "0123456789abcdef"[ ( u.w.w0 >> i ) & 0xf ];
	}
	
	/* Evaluate the unbiased exponent, if it's different from zero it's a */
	/* "normal" exponent.                                                 */
	if ( exponent != 0 )
	{
		/* Unbias the exponent. */
		exponent -= 1023;
	}
	/* Otherwise, it's an special exponent. */
	else
	{
		/* If the mantissa is zero, the exponent must be zero. */
		if ( zeroedm )
		{
			exponent = 0;
		}
		/* Otherwise, the exponent must be -1022 for a denormal. */
		else
		{
			exponent = -1022;
		}
	}
	
	/* Write the unbiased exponent with a sign. */
	*buffer++ = 'p';
	sprintf( buffer, "%+d", exponent );
}

/*
Returns the value of an hexadecimal digit.
*/
static uint hexvalue( char k )
{
	if ( k > '9' )
	{
		k = ( k & ~0x20 ) - 'A' + 10;
	}
	else
	{
		k -= '0';
	}
	
	return k;
}

/*
Transforms an hexadecimal float string into a double. The string is assumed to
be in the exact format as returned by dbltohex.
*/
static double hextodbl( const char* h )
{
	int  negative = 0;
	int  exponent;
	int  denormal;
	int  i;
	du_t u;
	
	/* Check the sign. */
	if ( *h == '-' )
	{
		negative = 1;
		h++;
	}
	
	/* Check for Infinity. */
	if ( *h == 'I' )
	{
		/* Set the exponent to 2047 and the mantissa to zero. */
		exponent = 0x7ff;
		u.w.w0 = u.w.w1 = 0;
	}
	/* Check for nan. */
	else if ( *h == 'N' )
	{
		/* Set the exponent to 2047 and the mantissa to all ones. */
		exponent = 0x7ff;
		u.w.w0 = 0xffffffffUL;
		u.w.w1 = 0x000fffffUL;
	}
	/* Otherwise it's a normal or denormal. */
	else
	{
		/* Skip the 0x prefix. */
		h += 2;
		
		/* Zero the mantissa. */
		u.w.w0 = u.w.w1 = 0;

		/* Note: this will also work for zeroes. */
		denormal = *h++ == '0';

		/* Skip the dot. */
		h++;
		
		/* Grab the fraction. */
		for ( i = 16; i >= 0; i -= 4 )
		{
			u.w.w1 |= hexvalue( *h++ ) << i;
		}
		for ( i = 28; i >= 0; i -= 4 )
		{
			u.w.w0 |= hexvalue( *h++ ) << i;
		}
		
		/* Set the exponent to zero if it's a denormal. */
		if ( denormal )
		{
			/* +/- 0 also have a zeroed exponent. */
			exponent = 0;
		}
		/* Otherwise, get the exponent. */
		else
		{
			/* Skip the 'p'. */
			h++;
			/* Get the biased exponent. */
			exponent = atoi( h ) + 1023;
		}
	}
	
	/* Assemble the rest of the number and return the double. */
	if ( negative )
	{
		u.w.w1 |= 0x80000000;
	}
	u.w.w1 |= exponent << 20;
	
	/* Swap u.w.w0 and u.w.w1 if the endian is big. */
	i = 1;
	if ( *(char*)&i == 0 )
	{
		uint temp = u.w.w0;
		u.w.w0 = u.w.w1;
		u.w.w1 = temp;
	}
	
	return u.d;
}