Add proper float formatting to vuprintf
Wanted to see how gnarly it is to do. Big number handling could be done with better algorithms since it can get a bit slow with large integers or tiny fractions with many lead zeros when only a few digits are needed. Anyway, it supports %e, %E, %f, %F, %g and %G. No %a or long double support seems warranted at the moment. Assumes IEEE 754 double format but it's laid out to be able to replace a function to handle others if needed. Tested in a driver program that has a duplicate vuprintf and the content was pasted in once it looked sound enough to put up a patch. Change-Id: I6dae8624d3208e644c88e36e6a17d8fc9144f988
This commit is contained in:
parent
3e2b50ed3b
commit
b70fecf21d
|
@ -199,6 +199,9 @@ libc/gmtime.c
|
|||
#endif /* CONFIG_PLATFORM || HAVE_ROCKBOX_C_LIBRARY */
|
||||
|
||||
/* Common */
|
||||
#ifndef BOOTLOADER
|
||||
common/ap_int.c
|
||||
#endif
|
||||
common/version.c
|
||||
common/config.c
|
||||
common/crc32.c
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2018 by Michael A. Sevakis
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#include "ap_int.h"
|
||||
#include "fixedpoint.h"
|
||||
|
||||
/* Miscellaneous large-sized integer functions */
|
||||
|
||||
/* round string, base 10 */
|
||||
bool round_number_string10(char *p_rdig, long len)
|
||||
{
|
||||
/*
|
||||
* * p should point to the digit that determines if rounding should occur
|
||||
* * buffer is updated in reverse
|
||||
* * an additional '1' may be added to the beginning: eg. 9.9 => 10.0
|
||||
*/
|
||||
#if 1 /* nearest */
|
||||
bool round = p_rdig[0] >= '5';
|
||||
#else /* even */
|
||||
bool round = p_rdig[0] >= '5' && (p_rdig[-1] & 1);
|
||||
#endif
|
||||
|
||||
while (round && len-- > 0) {
|
||||
int d = *--p_rdig;
|
||||
round = ++d > '9';
|
||||
*p_rdig = round ? '0' : d;
|
||||
}
|
||||
|
||||
if (round) {
|
||||
/* carry to the next place */
|
||||
*--p_rdig = '1';
|
||||
}
|
||||
|
||||
return round;
|
||||
}
|
||||
|
||||
/* format arbitrary-precision base 10 integer */
|
||||
char * format_ap_int10(struct ap_int *a,
|
||||
char *p_end)
|
||||
{
|
||||
/*
|
||||
* * chunks are in least-to-most-significant order
|
||||
* * chunk array is used for intermediate division results
|
||||
* * digit string buffer is written high-to-low address order
|
||||
*/
|
||||
long numchunks = a->numchunks;
|
||||
char *p = p_end;
|
||||
|
||||
if (numchunks == 0) {
|
||||
/* fast formatting */
|
||||
uint64_t val = a->val;
|
||||
|
||||
do {
|
||||
*--p = val % 10 + '0';
|
||||
val /= 10;
|
||||
} while (val);
|
||||
|
||||
a->len = p_end - p;
|
||||
return p;
|
||||
}
|
||||
|
||||
uint32_t *chunks = a->chunks;
|
||||
long topchunk = numchunks - 1;
|
||||
|
||||
/* if top chunk(s) are zero, ignore */
|
||||
while (topchunk >= 0 && chunks[topchunk] == 0) {
|
||||
topchunk--;
|
||||
}
|
||||
|
||||
/* optimized to divide number by the biggest 10^x a uint32_t can hold
|
||||
so that r_part holds the remainder (x % 1000000000) at the end of
|
||||
the division */
|
||||
do {
|
||||
uint64_t r_part = 0;
|
||||
|
||||
for (long i = topchunk; i >= 0; i--) {
|
||||
/*
|
||||
* Testing showed 29 bits as a sweet spot:
|
||||
* * Is a 32-bit constant (good for 32-bit hardware)
|
||||
* * No more normalization is required than with 30 and 31
|
||||
* (32 bits requires the least but also a large constant)
|
||||
* * Doesn't need to be reduced before hand by subtracting the
|
||||
* divisor in order to keep it 32-bits which obviates the need
|
||||
* to correct with another term of the remainder after
|
||||
* multiplying
|
||||
*
|
||||
* 2305843009 = floor(ldexp(1, 29) / 1000000000.0 * ldexp(1, 32))
|
||||
*/
|
||||
static const unsigned long c = 2305843009; /* .213693952 */
|
||||
uint64_t q_part = r_part*c >> 29;
|
||||
r_part = (r_part << 32) | chunks[i];
|
||||
r_part -= q_part*1000000000;
|
||||
|
||||
/* if remainder is still out of modular range, normalize it
|
||||
and carry over into quotient */
|
||||
while (r_part >= 1000000000) {
|
||||
r_part -= 1000000000;
|
||||
q_part++;
|
||||
}
|
||||
|
||||
chunks[i] = q_part;
|
||||
}
|
||||
|
||||
/* if top chunk(s) became zero, ignore from now on */
|
||||
while (topchunk >= 0 && chunks[topchunk] == 0) {
|
||||
topchunk--;
|
||||
}
|
||||
|
||||
/* format each digit chunk, padded to width 9 if not the leading one */
|
||||
uint32_t val = r_part;
|
||||
int len = 8*(topchunk >= 0);
|
||||
|
||||
while (len-- >= 0 || val) {
|
||||
*--p = (val % 10) + '0';
|
||||
val /= 10;
|
||||
}
|
||||
} while (topchunk >= 0);
|
||||
|
||||
a->len = p_end - p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* format arbitrary-precision base 10 fraction */
|
||||
char * format_ap_frac10(struct ap_int *a,
|
||||
char *p_start,
|
||||
long precision)
|
||||
{
|
||||
/*
|
||||
* * chunks are in least-to-most-significant order
|
||||
* * chunk array is used for intermediate multiplication results
|
||||
* * digit string buffer is written low-to-high address order
|
||||
* * high bit of fraction must be left-justified to a chunk
|
||||
* boundary
|
||||
*/
|
||||
long numchunks = a->numchunks;
|
||||
bool trimlz = precision < 0;
|
||||
char *p = p_start;
|
||||
|
||||
if (trimlz) {
|
||||
/* trim leading zeros and provide <precision> digits; a->len
|
||||
will end up greater than the specified precision unless the
|
||||
value is zero */
|
||||
precision = -precision;
|
||||
}
|
||||
|
||||
a->len = precision;
|
||||
|
||||
if (numchunks == 0) {
|
||||
/* fast formatting; shift must be <= 60 as top four bits are used
|
||||
for digit carryout */
|
||||
if (trimlz && !a->val) {
|
||||
/* value is zero */
|
||||
trimlz = false;
|
||||
}
|
||||
|
||||
uint64_t val = a->val << (60 - a->shift);
|
||||
|
||||
while (precision > 0) {
|
||||
val *= 10;
|
||||
uint32_t c_part = val >> 60;
|
||||
|
||||
if (trimlz) {
|
||||
if (!c_part) {
|
||||
a->len++;
|
||||
continue;
|
||||
}
|
||||
|
||||
trimlz = false;
|
||||
}
|
||||
|
||||
*p++ = c_part + '0';
|
||||
val ^= (uint64_t)c_part << 60;
|
||||
precision--;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
uint32_t *chunks = a->chunks;
|
||||
long bottomchunk = 0, topchunk = numchunks;
|
||||
|
||||
while (topchunk > 0 && chunks[topchunk - 1] == 0) {
|
||||
topchunk--;
|
||||
}
|
||||
|
||||
/* optimized to multiply number by the biggest 10^x a uint32_t can hold
|
||||
so that c_part holds the carryover into the integer part at the end
|
||||
of the multiplication */
|
||||
while (precision > 0) {
|
||||
/* if bottom chunk(s) are or became zero, skip them */
|
||||
while (bottomchunk < numchunks && chunks[bottomchunk] == 0) {
|
||||
bottomchunk++;
|
||||
}
|
||||
|
||||
uint32_t c_part = 0;
|
||||
|
||||
for (long i = bottomchunk; i < topchunk; i++) {
|
||||
uint64_t p_part = chunks[i];
|
||||
|
||||
p_part = p_part * 1000000000 + c_part;
|
||||
c_part = p_part >> 32;
|
||||
|
||||
chunks[i] = p_part;
|
||||
}
|
||||
|
||||
if (topchunk < numchunks && c_part) {
|
||||
chunks[topchunk++] = c_part;
|
||||
c_part = 0;
|
||||
}
|
||||
|
||||
int len = 9;
|
||||
|
||||
if (trimlz && bottomchunk < numchunks) {
|
||||
if (!c_part) {
|
||||
a->len += 9;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* first non-zero chunk has leading zeros? */
|
||||
for (uint32_t val = c_part; val < 100000000; val *= 10) {
|
||||
len--;
|
||||
}
|
||||
|
||||
a->len += 9 - len;
|
||||
trimlz = false;
|
||||
}
|
||||
|
||||
/* format each digit chunk, padded to width 9 if not exceeding
|
||||
precision */
|
||||
precision -= len;
|
||||
|
||||
if (precision < 0) {
|
||||
/* remove extra digits */
|
||||
c_part /= ipow(10, -precision);
|
||||
len += precision;
|
||||
}
|
||||
|
||||
p += len;
|
||||
|
||||
char *p2 = p;
|
||||
|
||||
while (len-- > 0) {
|
||||
*--p2 = (c_part % 10) + '0';
|
||||
c_part /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
|
@ -23,8 +23,10 @@
|
|||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include "system.h"
|
||||
#include "vuprintf.h"
|
||||
#include "ap_int.h"
|
||||
|
||||
#ifndef BOOTLOADER
|
||||
/* turn everything on */
|
||||
|
@ -58,7 +60,11 @@
|
|||
#define FMT_LENMOD_ll 0x010 /* signed/unsigned long long (%ll<radix>) */
|
||||
#define FMT_LENMOD_t 0x020 /* signed/unsigned ptrdiff_t (%t<radix>) */
|
||||
#define FMT_LENMOD_z 0x040 /* size_t/ssize_t (%z<radix>) */
|
||||
#if 0
|
||||
#define FMT_LENMOD_L 0x080 /* long double (instead of double) */
|
||||
#else
|
||||
#define FMT_LENMOD_L 0x000
|
||||
#endif
|
||||
|
||||
/* compulsory radixes: c, d, i, u, s */
|
||||
#define FMT_RADIX_c 0x001 /* single character (%c) */
|
||||
|
@ -75,6 +81,18 @@
|
|||
#define FMT_RADIX_g 0x800 /* floating point exponent or decimal depending
|
||||
upon value and precision */
|
||||
|
||||
/* TODO: 'a' 'A' */
|
||||
#define FMT_RADIX_floats (FMT_RADIX_e|FMT_RADIX_f|FMT_RADIX_g)
|
||||
|
||||
#if (FMT_RADIX & FMT_RADIX_floats)
|
||||
/* Assumes IEEE 754 double-precision, native-endian; replace to parse and init
|
||||
for some other format */
|
||||
#define parse_double parse_ieee754_double
|
||||
#define init_double_chunks init_ieee754_double_chunks
|
||||
#define format_double_int10 format_ap_int10
|
||||
#define format_double_frac10 format_ap_frac10
|
||||
#endif
|
||||
|
||||
/* avoid defining redundant functions if two or more types can use the same
|
||||
* something not getting a macro means it gets assigned its own value and
|
||||
* formatter */
|
||||
|
@ -374,8 +392,24 @@ struct fmt_buf {
|
|||
or prefix (numeric) */
|
||||
char buf[24]; /* work buffer */
|
||||
char bufend[1]; /* buffer end marker and guard '0' */
|
||||
#if (FMT_RADIX & FMT_RADIX_floats)
|
||||
int lenmod;
|
||||
int radixchar;
|
||||
int signchar;
|
||||
int alignchar;
|
||||
int width;
|
||||
int precision;
|
||||
char *p;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define PUSHCHAR(ch) \
|
||||
({ int __rc = push(userp, (ch)); \
|
||||
count += __rc >= 0; \
|
||||
if (__rc <= 0) { \
|
||||
goto done; \
|
||||
} })
|
||||
|
||||
/* %d %i */
|
||||
static inline const char * format_d(int val,
|
||||
struct fmt_buf *fmt_buf,
|
||||
|
@ -530,6 +564,400 @@ static inline const char * format_p(const void *p,
|
|||
}
|
||||
#endif /* FMT_RADIX_p */
|
||||
|
||||
#if (FMT_RADIX & FMT_RADIX_floats)
|
||||
/* find out how many uint32_t chunks need to be allocated, if any
|
||||
* if none are needed, finish the init for the number here */
|
||||
static long parse_ieee754_double(double f,
|
||||
struct ap_int *ia,
|
||||
struct ap_int *fa,
|
||||
struct fmt_buf *fmt_buf)
|
||||
{
|
||||
long rc = 0;
|
||||
|
||||
union {
|
||||
double f;
|
||||
uint64_t f64;
|
||||
} u = { .f = f };
|
||||
|
||||
int e = ((int)(u.f64 >> 52) & 0x7ff) - 1023; /* -1023..1024 */
|
||||
uint64_t mantissa = u.f64 & 0x000fffffffffffffull;
|
||||
|
||||
if (u.f64 >> 63) {
|
||||
fmt_buf->signchar = '-';
|
||||
}
|
||||
|
||||
if (LIKELY(e >= -8 && e <= 63)) { /* -8 to +63 */
|
||||
/* integer, fraction and manipulations fit in uint64_t */
|
||||
mantissa |= 0x0010000000000000ull;
|
||||
ia->numchunks = 0;
|
||||
ia->shift = 0;
|
||||
fa->numchunks = 0;
|
||||
|
||||
if (e < 0) { /* -8 to -1 - fraction */
|
||||
long fracbits = 52 - e;
|
||||
/* int - none */
|
||||
ia->len = 0;
|
||||
ia->val = 0;
|
||||
/* frac */
|
||||
fa->len = fracbits - __builtin_ctzll(mantissa);
|
||||
fa->shift = fracbits;
|
||||
fa->val = mantissa;
|
||||
}
|
||||
else if (e <= 51) { /* 0 to +51 - integer|fraction */
|
||||
long fracbits = 52 - e;
|
||||
/* int */
|
||||
ia->len = base10exp(e) + 2; /* go up + possibly 1 longer */
|
||||
ia->val = mantissa >> fracbits;
|
||||
/* frac */
|
||||
fa->shift = fracbits;
|
||||
fa->val = mantissa ^ (ia->val << fracbits);
|
||||
fa->len = fa->val ? fracbits - __builtin_ctzll(mantissa) : 0;
|
||||
}
|
||||
else { /* +52 to +63 - integer */
|
||||
/* int */
|
||||
ia->len = base10exp(e) + 2;
|
||||
ia->val = mantissa << (e - 52);
|
||||
/* frac - none */
|
||||
fa->len = 0;
|
||||
fa->shift = 0;
|
||||
fa->val = 0;
|
||||
}
|
||||
}
|
||||
else if (e < 0) { /* -1023 to -9 - fraction */
|
||||
/* int - none */
|
||||
ia->numchunks = 0;
|
||||
ia->len = 0;
|
||||
ia->shift = 0;
|
||||
ia->val = 0;
|
||||
/* frac - left-justify on bit 31 of the chunk of the MSb */
|
||||
if (e >= -1022) { /* normal */
|
||||
mantissa |= 0x0010000000000000ull;
|
||||
}
|
||||
else { /* subnormal (including zero) */
|
||||
e = -1022;
|
||||
}
|
||||
|
||||
if (mantissa) {
|
||||
long fracbits = 52 - e;
|
||||
fa->len = fracbits - __builtin_ctzll(mantissa);
|
||||
fa->shift = 31 - ((51 - e) % 32);
|
||||
fa->val = mantissa;
|
||||
fa->basechunk = (fa->shift + 52) / 32;
|
||||
fa->numchunks = (51 - e + fa->shift) / 32 + 1;
|
||||
rc = fa->numchunks;
|
||||
}
|
||||
else { /* zero */
|
||||
fa->numchunks = 0;
|
||||
fa->len = 0;
|
||||
fa->shift = 0;
|
||||
fa->val = 0;
|
||||
}
|
||||
}
|
||||
else if (e <= 1023) { /* +64 to +1023 - integer */
|
||||
/* int - right-justify on bit 0 of the first chunk */
|
||||
ia->val = mantissa | 0x0010000000000000ull;
|
||||
ia->len = base10exp(e) + 2;
|
||||
ia->shift = (e - 52) % 32;
|
||||
ia->basechunk = e / 32;
|
||||
ia->numchunks = ia->basechunk + 1;
|
||||
rc = ia->numchunks;
|
||||
/* frac - none */
|
||||
fa->numchunks = 0;
|
||||
fa->len = 0;
|
||||
fa->shift = 0;
|
||||
fa->val = 0;
|
||||
}
|
||||
else { /* +1024: INF, NAN */
|
||||
rc = -1 - !!mantissa;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* construct the arbitrary-precision value in the provided allocation */
|
||||
static void init_ieee754_double_chunks(struct ap_int *a,
|
||||
uint32_t *a_chunks)
|
||||
{
|
||||
long basechunk = a->basechunk;
|
||||
long shift = a->shift;
|
||||
uint64_t val = a->val;
|
||||
|
||||
a->chunks = a_chunks;
|
||||
|
||||
memset(a_chunks, 0, a->numchunks*sizeof (uint32_t));
|
||||
|
||||
if (shift < 12) {
|
||||
a_chunks[basechunk - 1] = val << shift;
|
||||
a_chunks[basechunk - 0] = val >> (32 - shift);
|
||||
}
|
||||
else {
|
||||
a_chunks[basechunk - 2] = val << shift;
|
||||
a_chunks[basechunk - 1] = val >> (32 - shift);
|
||||
a_chunks[basechunk - 0] = val >> (64 - shift);
|
||||
}
|
||||
}
|
||||
|
||||
/* format inf, nan strings */
|
||||
static void format_inf_nan(struct fmt_buf *fmt_buf, long type)
|
||||
{
|
||||
/* certain special values */
|
||||
static const char text[2][2][3] =
|
||||
{
|
||||
{ { 'I', 'N', 'F' }, { 'i', 'n', 'f' } },
|
||||
{ { 'N', 'A', 'N' }, { 'n', 'a', 'n' } },
|
||||
};
|
||||
|
||||
char *p = fmt_buf->buf;
|
||||
fmt_buf->p = p;
|
||||
fmt_buf->length = 3;
|
||||
|
||||
/* they also have a sign */
|
||||
if (fmt_buf->signchar) {
|
||||
*p++ = fmt_buf->signchar;
|
||||
fmt_buf->length++;
|
||||
}
|
||||
|
||||
memcpy(p, &text[type][(fmt_buf->radixchar >> 5) & 0x1], 3);
|
||||
}
|
||||
|
||||
/* %e %E %f %F %g %G */
|
||||
static int format_double_radix(double f,
|
||||
struct fmt_buf *fmt_buf,
|
||||
vuprintf_push_cb push,
|
||||
void *userp)
|
||||
{
|
||||
struct ap_int ia, fa;
|
||||
long rc = parse_double(f, &ia, &fa, fmt_buf);
|
||||
|
||||
if (UNLIKELY(rc < 0)) {
|
||||
format_inf_nan(fmt_buf, -rc - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
/* default precision is 6 for all formats */
|
||||
int prec_rem = fmt_buf->precision < 0 ? 6 : fmt_buf->precision;
|
||||
|
||||
int exp = exp;
|
||||
int explen = 0;
|
||||
|
||||
switch (fmt_buf->radixchar & 0x3)
|
||||
{
|
||||
case 3: /* %g, %G */
|
||||
fmt_buf->precision = prec_rem;
|
||||
if (prec_rem) {
|
||||
prec_rem--;
|
||||
}
|
||||
case 1: /* %e, %E */
|
||||
explen = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc > 0 && ia.numchunks > 0) {
|
||||
/* large integer required */
|
||||
init_double_chunks(&ia, alloca(rc*sizeof(*ia.chunks)));
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
const int bufoffs = 6; /* log rollover + round rollover + leading zeros (%g) */
|
||||
long f_prec = MIN(fa.len, prec_rem + 1);
|
||||
char buf[bufoffs + ia.len + f_prec + 1];
|
||||
char *p_last = &buf[bufoffs + ia.len];
|
||||
char *p_dec = p_last;
|
||||
char *p_first = format_double_int10(&ia, p_last);
|
||||
|
||||
if (explen) {
|
||||
if (!ia.val && fa.len) {
|
||||
p_first = p_last = &buf[bufoffs];
|
||||
f_prec = -f_prec - 1; /* no lead zeros */
|
||||
}
|
||||
else { /* handles 0e+0 too */
|
||||
exp = ia.len - 1;
|
||||
|
||||
if (exp) {
|
||||
prec_rem -= exp;
|
||||
|
||||
if (prec_rem < 0) {
|
||||
p_last += prec_rem + 1;
|
||||
f_prec = 0;
|
||||
}
|
||||
else {
|
||||
f_prec = MIN(fa.len, prec_rem + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p_dec = p_first + 1;
|
||||
}
|
||||
|
||||
if (f_prec) {
|
||||
if (rc > 0) {
|
||||
/* large integer required */
|
||||
init_double_chunks(&fa, alloca(rc*sizeof(*fa.chunks)));
|
||||
}
|
||||
|
||||
p_last = format_double_frac10(&fa, p_last, f_prec);
|
||||
|
||||
if (f_prec < 0) {
|
||||
f_prec = -f_prec - 1;
|
||||
exp = f_prec - fa.len;
|
||||
}
|
||||
|
||||
prec_rem -= f_prec;
|
||||
}
|
||||
|
||||
if (prec_rem < 0) {
|
||||
prec_rem = 0;
|
||||
p_last--;
|
||||
|
||||
if (round_number_string10(p_last, p_last - p_first)) {
|
||||
/* carried left */
|
||||
p_first--;
|
||||
|
||||
if (explen) {
|
||||
/* slide everything left by 1 */
|
||||
exp++;
|
||||
p_dec--;
|
||||
p_last--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (explen) {
|
||||
if ((fmt_buf->radixchar & 0x3) == 0x3) { /* g, G */
|
||||
/* 'g' is some weird crap */
|
||||
/* now that the final exponent is known and everything rounded,
|
||||
it is possible to decide whether to format similarly to
|
||||
'e' or 'f' */
|
||||
if (fmt_buf->precision > exp && exp >= -4) { /* P > X >= -4 */
|
||||
if (exp >= 0) {
|
||||
/* integer digits will be in the buffer */
|
||||
p_dec = p_first + exp + 1;
|
||||
}
|
||||
else {
|
||||
/* we didn't keep leading zeros and need to regenerate
|
||||
them; space was reserved just in case */
|
||||
p_first = memset(p_dec + exp - 1, '0', -exp);
|
||||
p_dec = p_first + 1;
|
||||
}
|
||||
|
||||
/* suppress exponent */
|
||||
explen = 0;
|
||||
}
|
||||
|
||||
if (!fmt_buf->length) {
|
||||
/* strip any trailing zeros from the fraction */
|
||||
while (p_last > p_dec && p_last[-1] == '0') {
|
||||
p_last--;
|
||||
}
|
||||
|
||||
/* suppress trailing precision fill */
|
||||
prec_rem = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (explen) {
|
||||
/* build exponent string: 'e±dd' */
|
||||
char *p = fmt_buf->bufend;
|
||||
int signchar = '+';
|
||||
|
||||
if (exp < 0) {
|
||||
signchar = '-';
|
||||
exp = -exp;
|
||||
}
|
||||
|
||||
while (exp || explen < 4) {
|
||||
*--p = exp % 10 + '0';
|
||||
exp /= 10;
|
||||
explen++;
|
||||
}
|
||||
|
||||
*--p = signchar;
|
||||
*--p = fmt_buf->radixchar & ~0x2;
|
||||
}
|
||||
}
|
||||
|
||||
int width = fmt_buf->width;
|
||||
int point = p_last > p_dec || prec_rem || fmt_buf->length;
|
||||
int length = p_last - p_first + !!fmt_buf->signchar + point + explen;
|
||||
|
||||
if (width) {
|
||||
if (width - length <= prec_rem) {
|
||||
width = 0;
|
||||
}
|
||||
else {
|
||||
width -= length + prec_rem;
|
||||
}
|
||||
}
|
||||
|
||||
rc = -1;
|
||||
|
||||
/* left padding */
|
||||
if (fmt_buf->alignchar > '0') {
|
||||
/* space-padded width -- before sign */
|
||||
while (width > 0) {
|
||||
PUSHCHAR(' ');
|
||||
width--;
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt_buf->signchar) {
|
||||
PUSHCHAR(fmt_buf->signchar);
|
||||
}
|
||||
|
||||
if (fmt_buf->alignchar == '0') {
|
||||
/* zero-padded width -- after sign */
|
||||
while (width > 0) {
|
||||
PUSHCHAR('0');
|
||||
width--;
|
||||
}
|
||||
}
|
||||
|
||||
/* integer part */
|
||||
while (p_first < p_dec) {
|
||||
PUSHCHAR(*p_first++);
|
||||
}
|
||||
|
||||
/* decimal point */
|
||||
if (point) {
|
||||
PUSHCHAR('.');
|
||||
}
|
||||
|
||||
/* fractional part */
|
||||
while (p_first < p_last) {
|
||||
PUSHCHAR(*p_first++);
|
||||
}
|
||||
|
||||
/* precision 0-padding */
|
||||
while (prec_rem > 0) {
|
||||
PUSHCHAR('0');
|
||||
prec_rem--;
|
||||
}
|
||||
|
||||
/* exponent */
|
||||
if (explen > 0) {
|
||||
char *p = fmt_buf->bufend;
|
||||
while (explen > 0) {
|
||||
PUSHCHAR(p[-explen--]);
|
||||
}
|
||||
}
|
||||
|
||||
/* right padding */
|
||||
while (width > 0) {
|
||||
PUSHCHAR(' ');
|
||||
width--;
|
||||
}
|
||||
|
||||
rc = 1;
|
||||
done:
|
||||
fmt_buf->length = count;
|
||||
return rc;
|
||||
}
|
||||
#endif /* FMT_RADIX_floats */
|
||||
|
||||
/* parse fixed width or precision field */
|
||||
static const char * parse_number_spec(const char *fmt,
|
||||
int ch,
|
||||
|
@ -558,16 +986,6 @@ int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
|
|||
const char *fmt,
|
||||
va_list ap)
|
||||
{
|
||||
#define PUSHCHAR(ch) \
|
||||
do { \
|
||||
int __ch = (ch); \
|
||||
int __rc = push(userp, __ch); \
|
||||
count += __rc >= 0; \
|
||||
if (__rc <= 0) { \
|
||||
goto done; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int count = 0;
|
||||
int ch;
|
||||
|
||||
|
@ -705,6 +1123,9 @@ int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
|
|||
#endif
|
||||
#if (FMT_LENMOD & FMT_LENMOD_z)
|
||||
case 'z':
|
||||
#endif
|
||||
#if (FMT_LENMOD & FMT_LENMOD_L)
|
||||
case 'L':
|
||||
#endif
|
||||
lenmod = ch;
|
||||
ch = *fmt++;
|
||||
|
@ -747,6 +1168,40 @@ int vuprintf(vuprintf_push_cb push, /* call 'push()' for each output letter */
|
|||
break;
|
||||
#endif
|
||||
|
||||
#if (FMT_RADIX & FMT_RADIX_floats)
|
||||
/* any floats gets all of them (except with 'L' and %a, %A for now) */
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'g':
|
||||
case 'G':
|
||||
/* LENMOD_L isn't supported for now and will be rejected automatically */
|
||||
|
||||
/* floating point has very different spec interpretations to other
|
||||
formats and requires special handling */
|
||||
fmt_buf.length = pfxlen;
|
||||
fmt_buf.lenmod = lenmod;
|
||||
fmt_buf.radixchar = ch;
|
||||
fmt_buf.signchar = signchar;
|
||||
fmt_buf.alignchar = alignchar;
|
||||
fmt_buf.width = width;
|
||||
fmt_buf.precision = precision;
|
||||
|
||||
ch = format_double_radix(va_arg(ap, double), &fmt_buf, push, userp);
|
||||
if (ch) {
|
||||
count += fmt_buf.length;
|
||||
if (ch > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
buf = fmt_buf.p;
|
||||
break;
|
||||
#endif
|
||||
|
||||
/** signed integer **/
|
||||
case 'd':
|
||||
case 'i':
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2018 by Michael A. Sevakis
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#ifndef AP_INT_H
|
||||
#define AP_INT_H
|
||||
|
||||
/* Miscellaneous large-sized integer functions */
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* return floor(log(2)*base2exp) - assists in estimating buffer sizes
|
||||
* when converting to decimal */
|
||||
static inline int base10exp(int base2exp)
|
||||
{
|
||||
/* 1292913986 = floor(2^32*log(2)) */
|
||||
static const long log10of2 = 1292913986L;
|
||||
return log10of2 * (int64_t)base2exp >> 32;
|
||||
}
|
||||
|
||||
struct ap_int
|
||||
{
|
||||
long numchunks; /* number of uint32_t chunks or zero */
|
||||
long basechunk; /* chunk of start of value bits */
|
||||
uint32_t *chunks; /* pointer to chunk array (caller alloced) */
|
||||
long len; /* length of output */
|
||||
long shift; /* number of fractional bits */
|
||||
uint64_t val; /* value, if it fits and numchunks is zero */
|
||||
};
|
||||
|
||||
bool round_number_string10(char *p_rdig, long len);
|
||||
|
||||
/* format arbitrary-precision base 10 integer */
|
||||
char * format_ap_int10(struct ap_int *a,
|
||||
char *p_end);
|
||||
|
||||
/* format arbitrary-precision base 10 fraction */
|
||||
char * format_ap_frac10(struct ap_int *a,
|
||||
char *p_start,
|
||||
long precision);
|
||||
|
||||
#endif /* AP_INT_H */
|
Loading…
Reference in New Issue