newlib-cygwin/newlib/libm/test/dcvt.c

595 lines
11 KiB
C

#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <float.h>
#include <ieeefp.h>
#include <stdlib.h>
#include <string.h>
#define _MAX_CHARS 512
static char *lcset = "0123456789abcdef";
static struct p {
double pvalue, nvalue;
int exp;
} powers[] =
{
{ 1e32, 1e-32, 32},
{ 1e16, 1e-16, 16},
{ 1e8, 1e-8, 8},
{ 1e4, 1e-4, 4},
{ 1e2, 1e-2, 2},
{ 1e1, 1e-1, 1 },
{ 1e0, 1e-0, 0 }
};
#define _MAX_PREC 16
static char
nextdigit (double *value)
{
double tmp;
*value = modf (*value * 10, &tmp) ;
return lcset[(int)tmp];
}
static char *
print_nan (char *buffer,
double value,
int precision)
{
size_t i;
if (isnan(value))
{
strcpy(buffer, "nan");
i = 3;
}
else
{
strcpy(buffer, "infinity");
i = 8;
}
while (i < precision)
{
buffer[i++] = ' ';
}
buffer[i++] = 0;
return buffer;
}
/* A convert info struct */
typedef struct
{
char *buffer ; /* Destination of conversion */
double value; /* scratch Value to convert */
double original_value; /* saved Value to convert */
int value_neg; /* OUT: 1 if value initialiy neg */
int abs_exp; /* abs Decimal exponent of value */
int abs_exp_sign; /* + or - */
int exp; /* exp not sgned */
int type; /* fFeEgG used in printing before exp */
int print_trailing_zeros; /* Print 00's after a . */
int null_idx; /* Index of the null at the end */
/* These ones are read only */
int decimal_places; /* the number of digits to print after
the decimal */
int max_digits; /* total number of digits to print */
int buffer_size; /* Size of output buffer */
/* Two sorts of dot ness.
0 never ever print a dot
1 print a dot if followed by a digit
2 always print a dot, even if no digit following
*/
enum { dot_never, dot_sometimes, dot_always} dot; /* Print a decimal point, always */
int dot_idx; /* where the dot went, or would have gone */
} cvt_info_type;
void
renormalize (cvt_info_type *in)
{
/* Make sure all numbers are less than 1 */
while (in->value >= 1.0)
{
in->value = in->value * 0.1;
in->exp++;
}
/* Now we have only numbers between 0 and .9999.., and have adjusted
exp to account for the shift */
if (in->exp >= 0)
{
in->abs_exp_sign = '+';
in->abs_exp = in->exp;
}
else
{
in->abs_exp_sign = '-';
in->abs_exp = -in->exp;
}
}
/* This routine looks at original_value, and makes it between 0 and 1,
modifying exp as it goes
*/
static void
normalize (double value,
cvt_info_type *in)
{
int j;
int texp;
if (value != 0)
{
texp = -1;
if (value < 0.0)
{
in->value_neg =1 ;
value = - value;
}
else
{
in->value_neg = 0;
}
/* Work out texponent & normalise value */
/* If value > 1, then shrink it */
if (value >= 1.0)
{
for (j = 0; j < 6; j++)
{
while (value >= powers[j].pvalue)
{
value /= powers[j].pvalue;
texp += powers[j].exp;
}
}
}
else if (value != 0.0)
{
for (j = 0; j < 6; j++)
{
while (value <= powers[j].nvalue)
{
value *= powers[j].pvalue;
texp -= powers[j].exp;
}
}
}
}
else
{
texp = 0;
}
in->exp = texp;
in->value = value;
in->original_value = value;
renormalize(in);
}
int
round (cvt_info_type *in,
char *start,
char *now,
char ch)
{
double rounder = 5.0;
char *p;
int ok = 0;
now --;
/* If the next digit to output would have been a '5' run back and */
/* see if we can create a more rounded number. If we can then do it.
If not (like when the number was 9.9 and the last char was
another 9), then we'll have to modify the number and try again */
if (ch < '5')
return 0;
for (p = now;!ok && p >= start; p--)
{
switch (*p)
{
default:
abort();
case '.':
break;
case '9':
rounder = rounder * 0.1;
break;
case '8':
case '7':
case '6':
case '5':
case '4':
case '3':
case '2':
case '1':
case '0':
p = now;
while (1) {
if (*p == '9') {
*p = '0';
}
else if (*p != '.') {
(*p)++;
return 0;
}
p--;
}
}
}
/* Getting here means that we couldn't round the number in place
textually - there have been all nines.
We'll have to add to it and try the conversion again
eg
.99999[9] can't be rounded in place, so add
.000005 to it giving:
1.000004 we notice that the result is > 1 so add to exp and
divide by 10
.100004
*/
in->original_value = in->value = in->original_value + rounder;
normalize(in->original_value , in);
return 1;
}
void
_cvte (register cvt_info_type *in)
{
int buffer_idx =0;
int digit = 0;
int after_decimal =0;
in->buffer[buffer_idx++] = nextdigit(&(in->value));
digit++;
in->dot_idx = buffer_idx;
switch (in->dot)
{
case dot_never:
break;
case dot_sometimes:
if (in->decimal_places
&& digit < in->max_digits)
{
in->buffer[buffer_idx++] = '.';
}
break;
case dot_always:
in->buffer[buffer_idx++] = '.';
}
while (buffer_idx < in->buffer_size
&& after_decimal < in->decimal_places
&& digit < in->max_digits)
{
in->buffer[buffer_idx] = nextdigit(&(in->value));
after_decimal++;
buffer_idx++;
digit++;
}
if (round(in,
in->buffer,
in->buffer+buffer_idx,
nextdigit(&(in->value))))
{
_cvte(in);
}
else
{
in->buffer[buffer_idx++] = in->type;
in->buffer[buffer_idx++] = in->abs_exp_sign;
if (in->abs_exp >= 100)
{
in->buffer[buffer_idx++] = lcset[in->abs_exp / 100];
in->abs_exp %= 100;
}
in->buffer[buffer_idx++] = lcset[in->abs_exp / 10];
in->buffer[buffer_idx++] = lcset[in->abs_exp % 10];
}
in->buffer[buffer_idx++] = 0;
}
/* Produce NNNN.FFFF */
void
_cvtf (cvt_info_type *in)
{
int buffer_idx = 0; /* Current char being output */
int after_decimal = 0;
int digit =0;
in->dot_idx = in->exp + 1;
/* Two sorts of number, NNN.FFF and 0.0000...FFFF */
/* Print all the digits up to the decimal point */
while (buffer_idx <= in->exp
&& digit < in->max_digits
&& buffer_idx < in->buffer_size)
{
in->buffer[buffer_idx] = nextdigit(&(in->value));
buffer_idx++;
digit ++;
}
/* And the decimal point if we should */
if (buffer_idx < in->buffer_size)
{
switch (in->dot)
{
case dot_never:
break;
case dot_sometimes:
/* Only print a dot if following chars */
if (in->decimal_places
&& digit < in->max_digits )
{
in->buffer[buffer_idx++] = '.';
}
break;
case dot_always:
in->buffer[buffer_idx++] = '.';
}
after_decimal = 0;
/* And the digits following the point if necessary */
/* Only print the leading zeros if a dot was possible */
if (in->dot || in->exp>0)
{
while (buffer_idx < in->buffer_size
&& (in->abs_exp_sign == '-' && digit < in->abs_exp - 1)
&& (after_decimal < in->decimal_places)
&& (digit < in->max_digits))
{
in->buffer[buffer_idx] = '0';
buffer_idx++;
digit++;
after_decimal++;
}
}
while (buffer_idx < in->buffer_size
&& after_decimal < in->decimal_places
&& digit < in->max_digits)
{
in->buffer[buffer_idx] = nextdigit(&(in->value));
buffer_idx++;
digit++;
after_decimal++;
}
}
in->null_idx = buffer_idx;
in->buffer[buffer_idx] = 0;
if (round(in, in->buffer, in->buffer+buffer_idx,
nextdigit(&(in->value))))
{
_cvtf(in);
}
}
char *
_dcvt (char *buffer,
double invalue,
int precision,
int width,
char type,
int dot)
{
cvt_info_type in;
in.buffer = buffer;
in.buffer_size = 512;
if (!finite(invalue))
{
return print_nan(buffer, invalue, precision);
}
normalize(invalue, &in);
in.type = type;
in.dot = dot? dot_always: dot_sometimes;
switch (type)
{
case 'g':
case 'G':
/* When formatting a g, the precision refers to the number of
char positions *total*, this leads to various off by ones */
{
/* A precision of 0 means 1 */
if (precision == 0)
precision = 1;
/* A g turns into an e if there are more digits than the
precision, or it's smaller than e-4 */
if (in.exp >= precision || in.exp < -4)
{
in.type = (type == 'g' ? 'e' : 'E');
in.decimal_places = _MAX_CHARS;
in.max_digits = precision;
in.print_trailing_zeros = 1;
_cvte(&in);
}
else
{
/* G means total number of chars to print */
in.decimal_places = _MAX_CHARS;
in.max_digits = precision;
in.type = (type == 'g' ? 'f' : 'F');
in.print_trailing_zeros = 0;
_cvtf(&in);
if (!dot) {
/* trim trailing zeros */
int j = in.null_idx -1;
while (j > 0 && in.buffer[j] == '0')
{
in.buffer[j] = 0;
j--;
}
/* Stamp on a . if not followed by zeros */
if (j > 0 && buffer[j] == '.')
in.buffer[j] = 0;
}
}
break;
case 'f':
case 'F':
in.decimal_places= precision;
in.max_digits = _MAX_CHARS;
in.print_trailing_zeros = 1;
_cvtf(&in);
break;
case 'e':
case 'E':
in.print_trailing_zeros = 1;
in.decimal_places = precision;
in.max_digits = _MAX_CHARS;
_cvte(&in);
break;
}
}
return buffer;
}
char *
fcvtbuf (double invalue,
int ndigit,
int *decpt,
int *sign,
char *fcvt_buf)
{
cvt_info_type in;
in.buffer = fcvt_buf;
in.buffer_size = 512;
if (!finite(invalue))
{
return print_nan(fcvt_buf, invalue, ndigit);
}
normalize(invalue, &in);
in.dot = dot_never; /* Don't print a decimal point */
in.max_digits = _MAX_CHARS;
in.buffer_size = _MAX_CHARS; /* Take as many as needed */
in.decimal_places = ndigit;
_cvtf(&in);
*decpt = in.dot_idx;
*sign = in.value_neg;
return in.buffer;
}
char *
ecvtbuf (double invalue,
int ndigit,
int *decpt,
int *sign,
char *fcvt_buf)
{
cvt_info_type in;
in.buffer = fcvt_buf;
if (!finite(invalue))
{
return print_nan(fcvt_buf, invalue, ndigit);
}
normalize(invalue, &in);
in.dot = dot_never; /* Don't print a decimal point */
/* We can work out how many digits go after the decimal point */
in.buffer_size =_MAX_CHARS;
in.decimal_places = _MAX_CHARS;
in.max_digits = ndigit; /* Take as many as told */
_cvtf(&in);
*decpt = in.dot_idx;
*sign = in.value_neg;
return in.buffer;
}
char *
gcvt (double d,
int ndigit,
char *buf)
{
return _dcvt(buf, d, ndigit, 0, 'g', 1);
}