cosmopolitan/libc/fmt/palandftoa.c

171 lines
6.3 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│
╚══════════════════════════════════════════════════════════════════════════════╝
│ @author (c) Marco Paland (info@paland.com) │
│ 2014-2019, PALANDesign Hannover, Germany │
│ │
│ @license The MIT License (MIT) │
│ │
│ Permission is hereby granted, free of charge, to any person obtaining a copy │
│ of this software and associated documentation files (the "Software"), to deal│
│ in the Software without restriction, including without limitation the rights │
│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │
│ copies of the Software, and to permit persons to whom the Software is │
│ furnished to do so, subject to the following conditions: │
│ │
│ The above copyright notice and this permission notice shall be included in │
│ all copies or substantial portions of the Software. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │
│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │
│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │
│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │
│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│
│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN │
│ THE SOFTWARE. │
│ │
│ @brief Tiny printf, sprintf and (v)snprintf implementation, optimized for │
│ embedded systems with a very limited resources. These routines are │
│ thread safe and reentrant! Use this instead of the bloated │
│ standard/newlib printf cause these use malloc for printf (and may not │
│ be thread safe). │
└─────────────────────────────────────────────────────────────────────────────*/
#include "libc/fmt/paland.inc"
#include "libc/fmt/palandprintf.internal.h"
#include "libc/math.h"
/**
* Formats floating point number.
*
* @see xdtoa() for higher precision at the cost of bloat
* @see palandprintf() which is intended caller
*/
int ftoa(int out(long, void *), void *arg, long double value, int prec,
unsigned long width, unsigned long flags) {
long whole, frac;
long double tmp, diff;
unsigned i, len, count, idx;
char buf[PRINTF_FTOA_BUFFER_SIZE];
len = 0;
diff = 0;
if (isnan(value)) {
buf[0] = 'n';
buf[1] = 'a';
buf[2] = 'n';
buf[3] = '\0';
len += 3;
} else if (isinf(value) || (value && ilogbl(fabsl(value)) > 63)) {
buf[0] = 'f';
buf[1] = 'n';
buf[2] = 'i';
buf[3] = '\0';
len += 3;
} else {
/* set default precision to 6, if not set explicitly */
if (!(flags & FLAGS_PRECISION)) {
prec = 6;
}
while (len < PRINTF_FTOA_BUFFER_SIZE && prec > 14) {
buf[len++] = '0';
prec--;
}
whole = truncl(fabsl(value));
tmp = (fabsl(value) - whole) * exp10l(prec);
frac = tmp;
diff = tmp - frac;
if (diff > .5) {
++frac; /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */
if (frac >= exp10l(prec)) {
frac = 0;
++whole;
}
} else if (diff < .5) {
} else if (!frac || (frac & 1)) {
++frac; /* if halfway, round up if odd OR if last digit is 0 */
}
if (!prec) {
diff = fabsl(value) - whole;
if ((!(diff < .5) || (diff > .5)) && (whole & 1)) {
/* exactly .5 and ODD, then round up */
/* 1.5 -> 2, but 2.5 -> 2 */
++whole;
}
} else {
count = prec;
/* now do fractional part, as an unsigned number */
while (len < PRINTF_FTOA_BUFFER_SIZE) {
--count;
buf[len++] = 48 + (frac % 10);
if (!(frac /= 10)) {
break;
}
}
/* add extra 0s */
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0)) {
buf[len++] = '0';
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
/* add decimal */
buf[len++] = '.';
}
}
/* do whole part, number is reversed */
while (len < PRINTF_FTOA_BUFFER_SIZE) {
buf[len++] = (char)(48 + (whole % 10));
if (!(whole /= 10)) {
break;
}
}
/* pad leading zeros */
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
if (width && (signbit(value) || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
if (signbit(value)) {
buf[len++] = '-';
} else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; /* ignore the space if the '+' exists */
} else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
/* pad spaces up to given width */
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
if (len < width) {
if (spacepad(out, arg, width - len) == -1) return -1;
}
}
/* reverse string */
for (idx = i = 0; i < len; i++) {
if (out(buf[len - i - 1U], arg) == -1) return -1;
idx++;
}
/* append pad spaces up to given width */
if (flags & FLAGS_LEFT) {
if (len < width) {
if (spacepad(out, arg, width - len) == -1) return -1;
}
}
return 0;
}