summaryrefslogtreecommitdiff
path: root/libc/misc/internals
diff options
context:
space:
mode:
authorEric Andersen <andersen@codepoet.org>2000-12-20 22:52:58 +0000
committerEric Andersen <andersen@codepoet.org>2000-12-20 22:52:58 +0000
commitc6218dbae579de0cd20f5a7f1e9877673e28225d (patch)
tree0fc8aaf54189b8ef6a2d130c12539814e0a724ee /libc/misc/internals
parent97112ff6f4f2a1dcd4c7f8a7512e0a4a02a2a332 (diff)
A number of updates from Manuel Novoa III. Things look good...
Diffstat (limited to 'libc/misc/internals')
-rw-r--r--libc/misc/internals/Makefile2
-rw-r--r--libc/misc/internals/dtostr.c271
-rw-r--r--libc/misc/internals/itoa.c21
-rw-r--r--libc/misc/internals/lltostr.c39
-rw-r--r--libc/misc/internals/ltoa.c40
-rw-r--r--libc/misc/internals/ltostr.c71
-rw-r--r--libc/misc/internals/ulltostr.c37
-rw-r--r--libc/misc/internals/ultostr.c37
-rw-r--r--libc/misc/internals/zoicheck.c16
9 files changed, 430 insertions, 104 deletions
diff --git a/libc/misc/internals/Makefile b/libc/misc/internals/Makefile
index 38bf7729f..b9168288e 100644
--- a/libc/misc/internals/Makefile
+++ b/libc/misc/internals/Makefile
@@ -24,7 +24,7 @@ TOPDIR=../../
include $(TOPDIR)Rules.mak
LIBC=$(TOPDIR)libc.a
-CSRC=itoa.c ltoa.c ltostr.c
+CSRC=ultostr.c ltostr.c ulltostr.c lltostr.c zoicheck.c dtostr.c
COBJS=$(patsubst %.c,%.o, $(CSRC))
OBJS=$(COBJS)
diff --git a/libc/misc/internals/dtostr.c b/libc/misc/internals/dtostr.c
new file mode 100644
index 000000000..d9ab80bbb
--- /dev/null
+++ b/libc/misc/internals/dtostr.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2000 Manuel Novoa III
+ *
+ * Function: const char *__dtostr(double x)
+ *
+ * This was written for uClibc to give it the ability to at least output
+ * floating point values in exponential form. No formatting is done.
+ * No trailing 0's are removed. Number of digits generated is not
+ * runtime selectable. It does however handle +/- infinity and nan on i386.
+ *
+ * The goal was usable floating point %e-type output in minimal size. For
+ * me, "gcc -c -Os -fomit-frame-pointer dtostr.c && size dtostr.o" gives
+ * text data bss dec hex filename
+ * 535 9 26 570 23a dtostr.o (WANT_EXP_FORM = 1)
+ * 530 9 26 565 235 dtostr.o (WANT_EXP_FORM = 0)
+ *
+ * Output is of the form [-][#].######{e|E}{+|-}##. Choices of upper or
+ * lower case exponent character, and initial digit/initial decimal forms
+ * are compile-time options. Number of digits generated is also selected
+ * at compile time (MAX_DIGITS).
+ *
+ * Notes:
+ *
+ * The primary objective of this implementation was minimal size while
+ * maintaining reasonable accuracy. It should also be fairly portable,
+ * as not assumptions are made about the bit-layout of doubles.
+ *
+ * It should be too difficult to convert this to handle long doubles on i386.
+ * For information, see the comments below.
+ *
+ * There are 2 compile-time options below, as well as some tuning parameters.
+ *
+ * TODO:
+ * long double and/or float version? (note: for float can trim code some).
+ */
+
+/*****************************************************************************/
+/* OPTIONS */
+/*****************************************************************************/
+
+/*
+ * Set this if you want output results with 1 digit before the decimal point.
+ * If this is 0, all digits follow a leading decimal point.
+ */
+#define WANT_EXP_FORM 1
+
+/*
+ * Set if you want exponent character 'E' rather than 'e'.
+ */
+#define EXP_UPPERCASE 1
+
+/*****************************************************************************/
+/* Don't change anything that follows unless you know what you're doing. */
+/*****************************************************************************/
+
+/*
+ * Configuration for the scaling power table. Ignoring denormals, you
+ * should have 2**EXP_TABLE_SIZE >= MAX_DBL_EXP >= 2**(EXP_TABLE_SIZE-1).
+ * The minimum for standard C is 6. For IEEE 8bit doubles, 9 suffices.
+ */
+#define EXP_TABLE_SIZE 9
+
+/*
+ * Set this to the maximum number of digits you want converted.
+ * Conversion is done in blocks of DIGITS_PER_BLOCK (9 by default) digits.
+ * 17 digits suffices to uniquely determine a double on i386.
+ */
+#define MAX_DIGITS 17
+
+/*
+ * This is really only used to check for infinities. The macro produces
+ * smaller code for i386 and, since this is tested before any floating point
+ * calculations, it doesn't appear to suffer from the excess precision problem
+ * caused by the FPU that strtod had. If it causes problems, call the function
+ * and compile zoicheck.c with -ffloat-store.
+ */
+#if 1
+#define _zero_or_inf_check(x) ( x == (x/4) )
+#else
+extern int _zero_or_inf_check(double x);
+#endif
+
+/*
+ * Fairly portable nan check. Bitwise for i386 generated larger code.
+ * If you have a better version, comment this out.
+ */
+#define isnan(x) (x != x)
+
+/*****************************************************************************/
+/* Don't change anything that follows peroid!!! ;-) */
+/*****************************************************************************/
+
+#include <float.h>
+#include <limits.h>
+
+/*
+ * Set things up for the scaling power table.
+ */
+
+#if EXP_TABLE_SIZE < 6
+#error EXP_TABLE_SIZE should be at least 6 to comply with standards
+#endif
+
+#define EXP_TABLE_MAX (1U<<(EXP_TABLE_SIZE-1))
+
+/*
+ * Only bother checking if this is too small.
+ * Throw in some play for denormals ( roughly O(-324) vs O(-307) on i386 ).
+ */
+
+#if (3+DBL_DIG-DBL_MIN_10_EXP)/2 > EXP_TABLE_MAX
+#error larger EXP_TABLE_SIZE needed
+#endif
+
+/*
+ * With 32 bit ints, we can get 9 digits per block.
+ */
+#define DIGITS_PER_BLOCK 9
+
+#if (INT_MAX >> 30)
+#define DIGIT_BLOCK_TYPE int
+#elif (LONG_MAX >> 30)
+#define DIGIT_BLOCK_TYPE long
+#else
+#error need at least 32 bit longs
+#endif
+
+/*
+ * This is kind of a place-holder for LONG_DOUBLE support to show what I
+ * think needs to be changed. I haven't tried it though. Changing this
+ * from 3 to 4 and converting double to long double should work on i386.
+ * DON'T FORGET to increase EXP_TABLE_SIZE and MAX_DIGITS.
+ * DON'T FORGET the "larger EXP_TABLE_SIZE needed" check above.
+ */
+#define MAX_EXP_DIGITS 3
+
+/*****************************************************************************/
+
+#define NUM_DIGIT_BLOCKS ((MAX_DIGITS+DIGITS_PER_BLOCK-1)/DIGITS_PER_BLOCK)
+
+static char infstr[] = " inf"; /* save space for a - sign */
+static char nanstr[] = "nan";
+
+/* extra space for '-', '.', 'e+###', and nul */
+/*static char buf[ 5 + MAX_EXP_DIGITS + NUM_DIGIT_BLOCKS * DIGITS_PER_BLOCK];*/
+#define BUF_SIZE ( 5 + MAX_EXP_DIGITS + NUM_DIGIT_BLOCKS * DIGITS_PER_BLOCK )
+/*****************************************************************************/
+
+const char *__dtostr(char *buf, double x)
+{
+ double exp_table[EXP_TABLE_SIZE];
+ double p10;
+ DIGIT_BLOCK_TYPE digit_block; /* int of at least 32 bits */
+ int i, j;
+ int exp, exp_neg;
+ int negative = 0;
+ char *pos;
+
+ if (isnan(x)) { /* nan check */
+ return nanstr;
+ }
+
+ if (x == 0) { /* handle 0 now to avoid false positive */
+ exp = 0; /* with inf test, and to avoid scaling */
+ goto GENERATE_DIGITS; /* note: time vs space tradeoff */
+ }
+
+ if (x < 0) { /* convert negatives to positives */
+ negative = 1;
+ x = -x;
+ }
+
+ if (_zero_or_inf_check(x)) { /* must be inf since zero handled above */
+ pos = infstr + 1;
+ goto DO_SIGN;
+ }
+
+ /* need to build the scaling table */
+ for (i = 0, p10 = 10 ; i < EXP_TABLE_SIZE ; i++) {
+ exp_table[i] = p10;
+ p10 *= p10;
+ }
+
+ exp_neg = 0;
+ if (x < 1e8) { /* do we need to scale up or down? */
+ exp_neg = 1;
+ }
+
+#if WANT_EXP_FORM
+ exp = DIGITS_PER_BLOCK - 1;
+#else
+ exp = DIGITS_PER_BLOCK;
+#endif
+
+ i = EXP_TABLE_SIZE;
+ j = EXP_TABLE_MAX;
+ while ( i-- ) { /* scale x such that 1e8 <= x < 1e9 */
+ if (exp_neg) {
+ if (x * exp_table[i] < 1e9) {
+ x *= exp_table[i];
+ exp -= j;
+ }
+ } else {
+ if (x / exp_table[i] >= 1e8) {
+ x /= exp_table[i];
+ exp += j;
+ }
+ }
+ j >>= 1;
+ }
+
+ GENERATE_DIGITS:
+ pos = buf - BUF_SIZE + 1 + DIGITS_PER_BLOCK + 1; /* leave space for '.' and - */
+
+ for (i = 0 ; i < NUM_DIGIT_BLOCKS ; ++i ) {
+ digit_block = (int) x;
+ x = (x - digit_block) * 1e9;
+ for (j = 0 ; j < DIGITS_PER_BLOCK ; j++) {
+ *--pos = '0' + (digit_block % 10);
+ digit_block /= 10;
+ }
+ pos += (2*DIGITS_PER_BLOCK);
+ }
+ pos -= (DIGITS_PER_BLOCK*(NUM_DIGIT_BLOCKS+1))-MAX_DIGITS;
+
+ /* start generating the exponent */
+#if EXP_UPPERCASE
+ *pos = 'E';
+#else
+ *pos = 'e';
+#endif
+ *++pos = '+';
+ if (exp < 0) {
+ *pos = '-';
+ exp = -exp;
+ }
+ pos += 3; /* WARNING: Assumes max exp < 1000!!! */
+ if (exp >= 100) {
+ ++pos;
+#if MAX_EXP_DIGITS > 4
+#error need to modify exponent string generation code
+#elif MAX_EXP_DIGITS > 3
+ if (exp >= 1000) { /* WARNING: hasn't been checked */
+ ++pos; /* but should work */
+ }
+#endif
+ }
+ *pos = '\0';
+
+ for (j = 0 ; (j < 2) || exp ; j++) { /* standard says at least 2 digits */
+ *--pos = '0' + (exp % 10);
+ exp /= 10;
+ }
+
+ /* insert the decimal point */
+ pos = buf - BUF_SIZE + 1;
+
+#if WANT_EXP_FORM
+ *pos = *(pos+1);
+ *(pos+1) = '.';
+#else
+ *pos = '.';
+#endif
+
+ DO_SIGN:
+ if (negative) {
+ *--pos = '-';
+ }
+
+ return pos;
+}
diff --git a/libc/misc/internals/itoa.c b/libc/misc/internals/itoa.c
index a683b8018..e69de29bb 100644
--- a/libc/misc/internals/itoa.c
+++ b/libc/misc/internals/itoa.c
@@ -1,21 +0,0 @@
-/* itoa.c <ndf@linux.mit.edu> */
-#define __MAX_INT_CHARS 7
-
-char *itoa(int i)
-{
- static char a[__MAX_INT_CHARS];
- char *b = a + sizeof(a) - 1;
- int sign = (i < 0);
-
- if (sign)
- i = -i;
- *b = 0;
- do {
- *--b = '0' + (i % 10);
- i /= 10;
- }
- while (i);
- if (sign)
- *--b = '-';
- return b;
-}
diff --git a/libc/misc/internals/lltostr.c b/libc/misc/internals/lltostr.c
new file mode 100644
index 000000000..2ce359615
--- /dev/null
+++ b/libc/misc/internals/lltostr.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2000 Manuel Novoa III
+ *
+ * Note: buf is a pointer to the END of the buffer passed.
+ * Call like this:
+ * char buf[SIZE], *p;
+ * p = __lltostr(buf + sizeof(buf) - 1, ...)
+ * For long longs of 64 bits, appropriate buffer sizes are:
+ * base = 2 66 = 1 (possible -) sign + 64 digits + 1 nul
+ * base = 10 21 = 1 (possible -) sign + 19 digits + 1 nul
+ * base = 16 18 = 1 (possible -) sign + 16 hex digits + 1 nul
+ */
+
+extern char *__ulltostr(char *buf, unsigned long long uval, int base,
+ int uppercase);
+
+char *__lltostr(char *buf, long long val, int base, int uppercase)
+{
+ unsigned long long uval;
+ char *pos;
+ int negative;
+
+ negative = 0;
+ if (val < 0) {
+ negative = 1;
+ uval = ((unsigned long long)(-(1+val))) + 1;
+ } else {
+ uval = val;
+ }
+
+
+ pos = __ulltostr(buf, uval, base, uppercase);
+
+ if (pos && negative) {
+ *--pos = '-';
+ }
+
+ return pos;
+}
diff --git a/libc/misc/internals/ltoa.c b/libc/misc/internals/ltoa.c
index da8b6d3df..e69de29bb 100644
--- a/libc/misc/internals/ltoa.c
+++ b/libc/misc/internals/ltoa.c
@@ -1,40 +0,0 @@
-/* Copyright (C) 1995,1996 Robert de Bath <rdebath@cix.compulink.co.uk>
- * This file is part of the Linux-8086 C library and is distributed
- * under the GNU Library General Public License.
- */
-
-static char buf[12];
-
-extern char *ultoa();
-
-char *ltoa(val)
-long val;
-{
- char *p;
- int flg = 0;
-
- if (val < 0) {
- flg++;
- val = -val;
- }
- p = ultoa(val);
- if (flg)
- *--p = '-';
- return p;
-}
-
-char *ultoa(val)
-unsigned long val;
-{
- char *p;
-
- p = buf + sizeof(buf);
- *--p = '\0';
-
- do {
- *--p = '0' + val % 10;
- val /= 10;
- }
- while (val);
- return p;
-}
diff --git a/libc/misc/internals/ltostr.c b/libc/misc/internals/ltostr.c
index da6fad232..7e45fec30 100644
--- a/libc/misc/internals/ltostr.c
+++ b/libc/misc/internals/ltostr.c
@@ -1,52 +1,39 @@
-/* Copyright (C) 1995,1996 Robert de Bath <rdebath@cix.compulink.co.uk>
- * This file is part of the Linux-8086 C library and is distributed
- * under the GNU Library General Public License.
+/*
+ * Copyright (C) 2000 Manuel Novoa III
+ *
+ * Note: buf is a pointer to the END of the buffer passed.
+ * Call like this:
+ * char buf[SIZE], *p;
+ * p = __ltostr(buf + sizeof(buf) - 1, ...)
+ *
+ * For longs of 32 bits, appropriate buffer sizes are:
+ * base = 2 34 = 1 (possible -) sign + 32 digits + 1 nul
+ * base = 10 12 = 1 (possible -) sign + 10 digits + 1 nul
+ * base = 16 10 = 1 (possible -) sign + 8 hex digits + 1 nul
*/
-static char buf[34];
+extern char *__ultostr(char *buf, unsigned long uval, int base, int uppercase);
-extern char *ultostr();
-
-char *ltostr(val, radix, uppercase)
-long val;
-int radix;
-int uppercase;
+char *__ltostr(char *buf, long val, int base, int uppercase)
{
- char *p;
- int flg = 0;
-
- if (val < 0) {
- flg++;
- val = -val;
+ unsigned long uval;
+ char *pos;
+ int negative;
+
+ negative = 0;
+ if (val < 0) {
+ negative = 1;
+ uval = ((unsigned long)(-(1+val))) + 1;
+ } else {
+ uval = val;
}
- p = ultostr(val, radix, uppercase);
- if (p && flg)
- *--p = '-';
- return p;
-}
-char *ultostr(val, radix, uppercase)
-unsigned long val;
-int radix;
-int uppercase;
-{
- register char *p;
- register int c;
- if (radix > 36 || radix < 2)
- return 0;
+ pos = __ultostr(buf, uval, base, uppercase);
- p = buf + sizeof(buf);
- *--p = '\0';
+ if (pos && negative) {
+ *--pos = '-';
+ }
- do {
- c = val % radix;
- val /= radix;
- if (c > 9)
- *--p = (uppercase ? 'A' : 'a') - 10 + c;
- else
- *--p = '0' + c;
- }
- while (val);
- return p;
+ return pos;
}
diff --git a/libc/misc/internals/ulltostr.c b/libc/misc/internals/ulltostr.c
new file mode 100644
index 000000000..50246d3bc
--- /dev/null
+++ b/libc/misc/internals/ulltostr.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2000 Manuel Novoa III
+ *
+ * Note: buf is a pointer to the END of the buffer passed.
+ * Call like this:
+ * char buf[SIZE], *p;
+ * p = __ulltostr(buf + sizeof(buf) - 1, ...)
+ *
+ * For long longs of 64 bits, appropriate buffer sizes are:
+ * base = 2 65 = 64 digits + 1 nul
+ * base = 10 20 = 19 digits + 1 nul
+ * base = 16 17 = 16 hex digits + 1 nul
+ */
+
+char *__ulltostr(char *buf, unsigned long long uval, int base, int uppercase)
+{
+ int digit;
+
+ if ((base < 2) || (base > 36)) {
+ return 0;
+ }
+
+ *buf = '\0';
+
+ do {
+ digit = uval % base;
+ uval /= base;
+
+ /* note: slightly slower but generates less code */
+ *--buf = '0' + digit;
+ if (digit > 9) {
+ *buf = (uppercase ? 'A' : 'a') + digit - 10;
+ }
+ } while (uval);
+
+ return buf;
+}
diff --git a/libc/misc/internals/ultostr.c b/libc/misc/internals/ultostr.c
new file mode 100644
index 000000000..d4663fb39
--- /dev/null
+++ b/libc/misc/internals/ultostr.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2000 Manuel Novoa III
+ *
+ * Note: buf is a pointer to the END of the buffer passed.
+ * Call like this;
+ * char buf[SIZE], *p;
+ * p = __ultostr(buf + sizeof(buf) - 1, ...)
+ *
+ * For longs of 32 bits, appropriate buffer sizes are:
+ * base = 2 33 = 32 digits + 1 nul
+ * base = 10 11 = 10 digits + 1 nul
+ * base = 16 9 = 8 hex digits + 1 nul
+ */
+
+char *__ultostr(char *buf, unsigned long uval, int base, int uppercase)
+{
+ int digit;
+
+ if ((base < 2) || (base > 36)) {
+ return 0;
+ }
+
+ *buf = '\0';
+
+ do {
+ digit = uval % base;
+ uval /= base;
+
+ /* note: slightly slower but generates less code */
+ *--buf = '0' + digit;
+ if (digit > 9) {
+ *buf = (uppercase ? 'A' : 'a') + digit - 10;
+ }
+ } while (uval);
+
+ return buf;
+}
diff --git a/libc/misc/internals/zoicheck.c b/libc/misc/internals/zoicheck.c
new file mode 100644
index 000000000..2113c6298
--- /dev/null
+++ b/libc/misc/internals/zoicheck.c
@@ -0,0 +1,16 @@
+
+/*
+ * Copyright (C) 2000 Manuel Novoa III
+ *
+ * This is a utility routine for strtod errno support.
+ * As the name implies, it checks if a double is either 0 or +/-infinity.
+ * Doing this inline doesn't work on i386 because of excess precission
+ * stored in the FPU.
+ *
+ * TODO: Check bitmasks directly?
+ */
+
+int _zero_or_inf_check(double x)
+{
+ return ( x == x/4 );
+}