mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-01 00:21:43 +00:00
c83ad9df6c
- removed use of finesine for creating the player backdrop for the menu display. This mostly uses the code from the old 2.0 floating point version but fixes some of the constants in there which were not correct.
1062 lines
26 KiB
C++
1062 lines
26 KiB
C++
/*
|
|
** zstrformat.cpp
|
|
** Routines for generic printf-style formatting.
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 2005-2008 Randy Heit
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
** Portions of this file relating to printing floating point numbers
|
|
** are covered by the following copyright:
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright (c) 1990, 1993
|
|
** The Regents of the University of California. All rights reserved.
|
|
**
|
|
** This code is derived from software contributed to Berkeley by
|
|
** Chris Torek.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 4. Neither the name of the University nor the names of its contributors
|
|
** may be used to endorse or promote products derived from this software
|
|
** without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
** ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
** SUCH DAMAGE.
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
** Even though the standard C library has a function to do printf-style
|
|
** formatting in a generic way, there is no standard interface to this
|
|
** function. So if you want to do some printf formatting that doesn't fit in
|
|
** the context of the provided functions, you need to roll your own. Why is
|
|
** that?
|
|
**
|
|
** Maybe Microsoft wants you to write a better one yourself? When used as
|
|
** part of a sprintf replacement, this function is significantly faster than
|
|
** Microsoft's offering. When used as part of a fprintf replacement, this
|
|
** function turns out to be slower, but that's probably because the CRT's
|
|
** fprintf can interact with the FILE object on a low level for better
|
|
** perfomance. If you sprintf into a buffer and then fwrite that buffer, this
|
|
** routine wins again, though the difference isn't great.
|
|
*/
|
|
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <locale.h>
|
|
|
|
#include "zstring.h"
|
|
#include "gdtoa.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
/*
|
|
* MAXEXPDIG is the maximum number of decimal digits needed to store a
|
|
* floating point exponent in the largest supported format. It should
|
|
* be ceil(log10(LDBL_MAX_10_EXP)) or, if hexadecimal floating point
|
|
* conversions are supported, ceil(log10(LDBL_MAX_EXP)). But since it
|
|
* is presently never greater than 5 in practice, we fudge it.
|
|
*/
|
|
#define MAXEXPDIG 6
|
|
#if LDBL_MAX_EXP > 999999
|
|
#error "floating point buffers too small"
|
|
#endif
|
|
|
|
#define DEFPREC 6
|
|
|
|
static const char hexits[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
|
|
static const char HEXits[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
|
|
static const char spaces[16] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
|
|
static const char zeroes[17] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','.'};
|
|
static const char dotchar = '.';
|
|
|
|
namespace StringFormat
|
|
{
|
|
static int writepad (OutputFunc output, void *outputData, const char *pad, int padsize, int spaceToFill);
|
|
static int printandpad (OutputFunc output, void *outputData, const char *p, const char *ep, int len, const char *with, int padsize);
|
|
static int exponent (char *p0, int exp, int fmtch);
|
|
|
|
int Worker (OutputFunc output, void *outputData, const char *fmt, ...)
|
|
{
|
|
va_list arglist;
|
|
int len;
|
|
|
|
va_start (arglist, fmt);
|
|
len = VWorker (output, outputData, fmt, arglist);
|
|
va_end (arglist);
|
|
return len;
|
|
}
|
|
|
|
int VWorker (OutputFunc output, void *outputData, const char *fmt, va_list arglist)
|
|
{
|
|
const char *c;
|
|
const char *base;
|
|
int len = 0;
|
|
int width;
|
|
int precision;
|
|
int flags;
|
|
|
|
base = c = fmt;
|
|
for (;;)
|
|
{
|
|
while (*c && *c != '%')
|
|
{
|
|
++c;
|
|
}
|
|
if (*c == '\0')
|
|
{
|
|
return len + output (outputData, base, int(c - base));
|
|
}
|
|
|
|
if (c - base > 0)
|
|
{
|
|
len += output (outputData, base, int(c - base));
|
|
}
|
|
c++;
|
|
|
|
// Gather the flags, if any
|
|
for (flags = 0;; ++c)
|
|
{
|
|
if (*c == '-')
|
|
{
|
|
flags |= F_MINUS; // bit 0
|
|
}
|
|
else if (*c == '+')
|
|
{
|
|
flags |= F_PLUS; // bit 1
|
|
}
|
|
else if (*c == '0')
|
|
{
|
|
flags |= F_ZERO; // bit 2
|
|
}
|
|
else if (*c == ' ')
|
|
{
|
|
flags |= F_BLANK; // bit 3
|
|
}
|
|
else if (*c == '#')
|
|
{
|
|
flags |= F_HASH; // bit 4
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
width = precision = -1;
|
|
|
|
// Read the width, if any
|
|
if (*c == '*')
|
|
{
|
|
++c;
|
|
width = va_arg (arglist, int);
|
|
if (width < 0)
|
|
{ // Negative width means minus flag and positive width
|
|
flags |= F_MINUS;
|
|
width = -width;
|
|
}
|
|
}
|
|
else if (*c >= '0' && *c <= '9')
|
|
{
|
|
width = *c++ - '0';
|
|
while (*c >= '0' && *c <= '9')
|
|
{
|
|
width = width * 10 + *c++ - '0';
|
|
}
|
|
}
|
|
|
|
// If 0 and - both appear, 0 is ignored.
|
|
// If the blank and + both appear, the blank is ignored.
|
|
flags &= ~((flags & 3) << 2);
|
|
|
|
// Read the precision, if any
|
|
if (*c == '.')
|
|
{
|
|
precision = 0;
|
|
if (*++c == '*')
|
|
{
|
|
++c;
|
|
precision = va_arg (arglist, int);
|
|
}
|
|
else if (*c >= '0' && *c <= '9')
|
|
{
|
|
precision = *c++ - '0';
|
|
while (*c >= '0' && *c <= '9')
|
|
{
|
|
precision = precision * 10 + *c++ - '0';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the size prefix, if any
|
|
if (*c == 'h')
|
|
{
|
|
if (*++c == 'h')
|
|
{
|
|
flags |= F_HALFHALF;
|
|
++c;
|
|
}
|
|
else
|
|
{
|
|
flags |= F_HALF;
|
|
}
|
|
}
|
|
else if (*c == 'l')
|
|
{
|
|
if (*++c == 'l')
|
|
{
|
|
flags |= F_LONGLONG;
|
|
++c;
|
|
}
|
|
else
|
|
{
|
|
flags |= F_LONG;
|
|
}
|
|
}
|
|
else if (*c == 'I')
|
|
{
|
|
if (*++c == '6')
|
|
{
|
|
if (*++c == '4')
|
|
{
|
|
flags |= F_LONGLONG;
|
|
++c;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
flags |= F_BIGI;
|
|
}
|
|
}
|
|
else if (*c == 't')
|
|
{
|
|
flags |= F_PTRDIFF;
|
|
++c;
|
|
}
|
|
else if (*c == 'z')
|
|
{
|
|
flags |= F_SIZE;
|
|
++c;
|
|
}
|
|
|
|
base = c+1;
|
|
|
|
// Now that that's all out of the way, we should be pointing at the type specifier
|
|
{
|
|
char prefix[3];
|
|
int prefixlen;
|
|
char hexprefix = '\0';
|
|
char sign = '\0';
|
|
int postprefixzeros = 0;
|
|
int size = flags & 0xF000;
|
|
char buffer[80], *ibuff;
|
|
const char *obuff = 0;
|
|
char type = *c++;
|
|
int bufflen = 0;
|
|
int outlen = 0;
|
|
unsigned int intarg = 0;
|
|
uint64_t int64arg = 0;
|
|
const void *voidparg;
|
|
const char *charparg;
|
|
double dblarg;
|
|
const char *xits = hexits;
|
|
int inlen = len;
|
|
/*
|
|
* We can decompose the printed representation of floating
|
|
* point numbers into several parts, some of which may be empty:
|
|
*
|
|
* [+|-| ] [0x|0X] MMM . NNN [e|E|p|P] [+|-] ZZ
|
|
* A B ---C--- D E F
|
|
*
|
|
* A: 'sign' holds this value if present; '\0' otherwise
|
|
* B: hexprefix holds the 'x' or 'X'; '\0' if not hexadecimal
|
|
* C: obuff points to the string MMMNNN. Leading and trailing
|
|
* zeros are not in the string and must be added.
|
|
* D: expchar holds this character; '\0' if no exponent, e.g. %f
|
|
* F: at least two digits for decimal, at least one digit for hex
|
|
*/
|
|
const char *decimal_point = ".";/* locale specific decimal point */
|
|
int signflag; /* true if float is negative */
|
|
int expt; /* integer value of exponent */
|
|
char expchar = 'e'; /* exponent character: [eEpP\0] */
|
|
char *dtoaend; /* pointer to end of converted digits */
|
|
int expsize = 0; /* character count for expstr */
|
|
int ndig = 0; /* actual number of digits returned by dtoa */
|
|
char expstr[MAXEXPDIG+2]; /* buffer for exponent string: e+ZZZ */
|
|
char *dtoaresult = NULL; /* buffer allocated by dtoa */
|
|
|
|
// Using a bunch of if/else if statements is faster than a switch, because a switch generates
|
|
// a jump table. A jump table means a possible data cache miss and a hefty penalty while the
|
|
// cache line is loaded.
|
|
|
|
if (type == 'x' || type == 'X' ||
|
|
type == 'p' ||
|
|
type == 'd' || type == 'u' || type == 'i' ||
|
|
type == 'o' ||
|
|
type == 'B')
|
|
{
|
|
if (type == 'X' || type == 'p')
|
|
{
|
|
xits = HEXits;
|
|
}
|
|
if (type == 'p')
|
|
{
|
|
type = 'X';
|
|
voidparg = va_arg (arglist, void *);
|
|
if (sizeof(void*) == sizeof(int))
|
|
{
|
|
intarg = (unsigned int)(size_t)voidparg;
|
|
precision = 8;
|
|
size = 0;
|
|
}
|
|
else
|
|
{
|
|
int64arg = (uint64_t)(size_t)voidparg;
|
|
precision = 16;
|
|
size = F_LONGLONG;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (size == 0)
|
|
{
|
|
intarg = va_arg (arglist, int);
|
|
}
|
|
else if (size == F_HALFHALF)
|
|
{
|
|
intarg = va_arg (arglist, int);
|
|
intarg = (signed char)intarg;
|
|
}
|
|
else if (size == F_HALF)
|
|
{
|
|
intarg = va_arg (arglist, int);
|
|
intarg = (short)intarg;
|
|
}
|
|
else if (size == F_LONG)
|
|
{
|
|
if (sizeof(long) == sizeof(int)) intarg = va_arg (arglist, int);
|
|
else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
|
|
}
|
|
else if (size == F_BIGI)
|
|
{
|
|
if (sizeof(void*) == sizeof(int)) intarg = va_arg (arglist, int);
|
|
else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
|
|
}
|
|
else if (size == F_LONGLONG)
|
|
{
|
|
int64arg = va_arg (arglist, int64_t);
|
|
}
|
|
else if (size == F_PTRDIFF)
|
|
{
|
|
if (sizeof(ptrdiff_t) == sizeof(int)) intarg = va_arg (arglist, int);
|
|
else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
|
|
}
|
|
else if (size == F_SIZE)
|
|
{
|
|
if (sizeof(size_t) == sizeof(int)) intarg = va_arg (arglist, int);
|
|
else { int64arg = va_arg (arglist, int64_t); size = F_LONGLONG; }
|
|
}
|
|
else
|
|
{
|
|
intarg = va_arg (arglist, int);
|
|
}
|
|
}
|
|
|
|
if (precision < 0) precision = 1;
|
|
|
|
ibuff = &buffer[sizeof(buffer)];
|
|
|
|
if (size == F_LONGLONG)
|
|
{
|
|
if (int64arg == 0)
|
|
{
|
|
flags |= F_ZEROVALUE;
|
|
}
|
|
else
|
|
{
|
|
if (type == 'o')
|
|
{ // Octal: Dump digits until it fits in an unsigned int
|
|
while (int64arg > UINT_MAX)
|
|
{
|
|
*--ibuff = char(int64arg & 7) + '0'; int64arg >>= 3;
|
|
}
|
|
intarg = int(int64arg);
|
|
}
|
|
else if (type == 'x' || type == 'X')
|
|
{ // Hexadecimal: Dump digits until it fits in an unsigned int
|
|
while (int64arg > UINT_MAX)
|
|
{
|
|
*--ibuff = xits[int64arg & 15]; int64arg >>= 4;
|
|
}
|
|
intarg = int(int64arg);
|
|
}
|
|
else if (type == 'B')
|
|
{ // Binary: Dump digits until it fits in an unsigned int
|
|
while (int64arg > UINT_MAX)
|
|
{
|
|
*--ibuff = char(int64arg & 1) + '0'; int64arg >>= 1;
|
|
}
|
|
intarg = int(int64arg);
|
|
}
|
|
else
|
|
{
|
|
if (type != 'u')
|
|
{
|
|
// If a signed number is negative, set the negative flag and make it positive.
|
|
int64_t sint64arg = (int64_t)int64arg;
|
|
if (sint64arg < 0)
|
|
{
|
|
flags |= F_NEGATIVE;
|
|
sint64arg = -sint64arg;
|
|
int64arg = sint64arg;
|
|
}
|
|
flags |= F_SIGNED;
|
|
type = 'u';
|
|
}
|
|
// If an unsigned int64 is too big to fit in an unsigned int, dump out
|
|
// digits until it is sufficiently small.
|
|
while (int64arg > INT_MAX)
|
|
{
|
|
*--ibuff = char(int64arg % 10) + '0'; int64arg /= 10;
|
|
}
|
|
intarg = (unsigned int)(int64arg);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (intarg == 0)
|
|
{
|
|
flags |= F_ZEROVALUE;
|
|
}
|
|
else if (type == 'i' || type == 'd')
|
|
{ // If a signed int is negative, set the negative flag and make it positive.
|
|
signed int sintarg = (signed int)intarg;
|
|
if (sintarg < 0)
|
|
{
|
|
flags |= F_NEGATIVE;
|
|
sintarg = -sintarg;
|
|
intarg = sintarg;
|
|
}
|
|
flags |= F_SIGNED;
|
|
type = 'u';
|
|
}
|
|
}
|
|
if (flags & F_ZEROVALUE)
|
|
{
|
|
if (precision != 0)
|
|
{
|
|
*--ibuff = '0';
|
|
}
|
|
}
|
|
else if (type == 'u')
|
|
{ // Decimal
|
|
int i;
|
|
|
|
// Unsigned division is typically slower than signed division.
|
|
// Do it at most once.
|
|
if (intarg > INT_MAX)
|
|
{
|
|
*--ibuff = char(intarg % 10) + '0'; intarg /= 10;
|
|
}
|
|
i = (int)intarg;
|
|
while (i != 0)
|
|
{
|
|
*--ibuff = char(i % 10) + '0'; i /= 10;
|
|
}
|
|
}
|
|
else if (type == 'o')
|
|
{ // Octal
|
|
while (intarg != 0)
|
|
{
|
|
*--ibuff = char(intarg & 7) + '0'; intarg >>= 3;
|
|
}
|
|
}
|
|
else if (type == 'B')
|
|
{ // Binary
|
|
while (intarg != 0)
|
|
{
|
|
*--ibuff = char(intarg & 1) + '0'; intarg >>= 1;
|
|
}
|
|
}
|
|
else
|
|
{ // Hexadecimal
|
|
while (intarg != 0)
|
|
{
|
|
*--ibuff = xits[intarg & 15]; intarg >>= 4;
|
|
}
|
|
}
|
|
// Check for prefix (only for non-decimal, which are always unsigned)
|
|
if ((flags & (F_HASH|F_ZEROVALUE)) == F_HASH)
|
|
{
|
|
if (type == 'o')
|
|
{
|
|
if (bufflen >= precision)
|
|
{
|
|
sign = '0';
|
|
}
|
|
}
|
|
else if (type == 'x' || type == 'X')
|
|
{
|
|
hexprefix = type;
|
|
}
|
|
else if (type == 'B')
|
|
{
|
|
hexprefix = '!';
|
|
}
|
|
}
|
|
bufflen = (int)(ptrdiff_t)(&buffer[sizeof(buffer)] - ibuff);
|
|
obuff = ibuff;
|
|
if (precision >= 0)
|
|
{
|
|
postprefixzeros = precision - bufflen;
|
|
if (postprefixzeros < 0) postprefixzeros = 0;
|
|
// flags &= ~F_ZERO;
|
|
}
|
|
}
|
|
else if (type == 'c')
|
|
{
|
|
intarg = va_arg (arglist, int);
|
|
buffer[0] = char(intarg);
|
|
bufflen = 1;
|
|
obuff = buffer;
|
|
}
|
|
else if (type == 's')
|
|
{
|
|
charparg = va_arg (arglist, const char *);
|
|
if (charparg == NULL)
|
|
{
|
|
obuff = "(null)";
|
|
bufflen = 6;
|
|
}
|
|
else
|
|
{
|
|
obuff = charparg;
|
|
if (precision < 0)
|
|
{
|
|
bufflen = (int)strlen (charparg);
|
|
}
|
|
else
|
|
{
|
|
for (bufflen = 0; bufflen < precision && charparg[bufflen] != '\0'; ++bufflen)
|
|
{ /* empty */ }
|
|
}
|
|
}
|
|
}
|
|
else if (type == '%')
|
|
{ // Just print a '%': Output it with the next stage.
|
|
base--;
|
|
continue;
|
|
}
|
|
else if (type == 'n')
|
|
{
|
|
if (size == F_HALFHALF)
|
|
{
|
|
*va_arg (arglist, char *) = (char)inlen;
|
|
}
|
|
else if (size == F_HALF)
|
|
{
|
|
*va_arg (arglist, short *) = (short)inlen;
|
|
}
|
|
else if (size == F_LONG)
|
|
{
|
|
*va_arg (arglist, long *) = inlen;
|
|
}
|
|
else if (size == F_LONGLONG)
|
|
{
|
|
*va_arg (arglist, int64_t *) = inlen;
|
|
}
|
|
else if (size == F_BIGI)
|
|
{
|
|
*va_arg (arglist, ptrdiff_t *) = inlen;
|
|
}
|
|
else
|
|
{
|
|
*va_arg (arglist, int *) = inlen;
|
|
}
|
|
}
|
|
else if (type == 'f' || type == 'F')
|
|
{
|
|
expchar = '\0';
|
|
goto fp_begin;
|
|
}
|
|
else if (type == 'g' || type == 'G')
|
|
{
|
|
expchar = type - ('g' - 'e');
|
|
if (precision == 0)
|
|
{
|
|
precision = 1;
|
|
}
|
|
goto fp_begin;
|
|
}
|
|
else if (type == 'H')
|
|
{ // %H is an extension that behaves similarly to %g, except it automatically
|
|
// selects precision based on whatever will produce the smallest string.
|
|
expchar = 'e';
|
|
goto fp_begin;
|
|
}
|
|
#if 0
|
|
// The hdtoa function provided with FreeBSD uses a hexadecimal FP constant.
|
|
// Microsoft's compiler does not support these, so I would need to hack it
|
|
// together with ints instead. It's very do-able, but until I actually have
|
|
// some reason to print hex FP numbers, I won't bother.
|
|
else if (type == 'a' || type == 'A')
|
|
{
|
|
if (type == 'A')
|
|
{
|
|
xits = HEXits;
|
|
hexprefix = 'X';
|
|
expchar = 'P';
|
|
}
|
|
else
|
|
{
|
|
hexprefix = 'x';
|
|
expchar = 'p';
|
|
}
|
|
if (precision >= 0)
|
|
{
|
|
precision++;
|
|
}
|
|
dblarg = va_arg(arglist, double);
|
|
dtoaresult = obuff = hdtoa(dblarg, xits, precision, &expt, &signflag, &dtoaend);
|
|
if (precision < 0)
|
|
{
|
|
precision = (int)(dtoaend - obuff);
|
|
}
|
|
if (expt == INT_MAX)
|
|
{
|
|
hexprefix = '\0';
|
|
}
|
|
goto fp_common;
|
|
}
|
|
#endif
|
|
else if (type == 'e' || type == 'E')
|
|
{
|
|
expchar = type;
|
|
if (precision < 0) // account for digit before decpt
|
|
{
|
|
precision = DEFPREC + 1;
|
|
}
|
|
else
|
|
{
|
|
precision++;
|
|
}
|
|
fp_begin:
|
|
if (precision < 0)
|
|
{
|
|
precision = DEFPREC;
|
|
}
|
|
dblarg = va_arg(arglist, double);
|
|
obuff = dtoaresult = dtoa(dblarg, type != 'H' ? (expchar ? 2 : 3) : 0, precision, &expt, &signflag, &dtoaend);
|
|
//fp_common:
|
|
decimal_point = localeconv()->decimal_point;
|
|
flags |= F_SIGNED;
|
|
if (signflag)
|
|
{
|
|
flags |= F_NEGATIVE;
|
|
}
|
|
if (expt == INT_MAX) // inf or nan
|
|
{
|
|
if (*obuff == 'N')
|
|
{
|
|
obuff = (type >= 'a') ? "nan" : "NAN";
|
|
flags &= ~F_SIGNED;
|
|
}
|
|
else
|
|
{
|
|
obuff = (type >= 'a') ? "inf" : "INF";
|
|
}
|
|
bufflen = 3;
|
|
flags &= ~F_ZERO;
|
|
}
|
|
else
|
|
{
|
|
flags |= F_FPT;
|
|
ndig = (int)(dtoaend - obuff);
|
|
if (type == 'g' || type == 'G')
|
|
{
|
|
if (expt > -4 && expt <= precision)
|
|
{ // Make %[gG] smell like %[fF].
|
|
expchar = '\0';
|
|
if (flags & F_HASH)
|
|
{
|
|
precision -= expt;
|
|
}
|
|
else
|
|
{
|
|
precision = ndig - expt;
|
|
}
|
|
if (precision < 0)
|
|
{
|
|
precision = 0;
|
|
}
|
|
}
|
|
else
|
|
{ // Make %[gG] smell like %[eE], but trim trailing zeroes if no # flag.
|
|
if (!(flags & F_HASH))
|
|
{
|
|
precision = ndig;
|
|
}
|
|
}
|
|
}
|
|
else if (type == 'H')
|
|
{
|
|
if (expt > -(ndig + 2) && expt <= (ndig + 4))
|
|
{ // Make %H smell like %f
|
|
expchar = '\0';
|
|
precision = ndig - expt;
|
|
if (precision < 0)
|
|
{
|
|
precision = 0;
|
|
}
|
|
}
|
|
else
|
|
{// Make %H smell like %e
|
|
precision = ndig;
|
|
}
|
|
}
|
|
if (expchar)
|
|
{
|
|
expsize = exponent(expstr, expt - 1, expchar);
|
|
bufflen = expsize + precision;
|
|
if (precision > 1 || (flags & F_HASH))
|
|
{
|
|
++bufflen;
|
|
}
|
|
}
|
|
else
|
|
{ // space for digits before decimal point
|
|
if (expt > 0)
|
|
{
|
|
bufflen = expt;
|
|
}
|
|
else // "0"
|
|
{
|
|
bufflen = 1;
|
|
}
|
|
// space for decimal pt and following digits
|
|
if (precision != 0 || (flags & F_HASH))
|
|
{
|
|
bufflen += precision + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for sign prefix (only for signed numbers)
|
|
if (flags & F_SIGNED)
|
|
{
|
|
if (flags & F_NEGATIVE)
|
|
{
|
|
sign = '-';
|
|
}
|
|
else if (flags & F_PLUS)
|
|
{
|
|
sign = '+';
|
|
}
|
|
else if (flags & F_BLANK)
|
|
{
|
|
sign = ' ';
|
|
}
|
|
}
|
|
|
|
// Construct complete prefix from sign and hex prefix character
|
|
prefixlen = 0;
|
|
if (sign != '\0')
|
|
{
|
|
prefix[0] = sign;
|
|
prefixlen = 1;
|
|
}
|
|
if (hexprefix != '\0')
|
|
{
|
|
prefix[prefixlen] = '0';
|
|
prefix[prefixlen + 1] = hexprefix;
|
|
prefixlen += 2;
|
|
}
|
|
|
|
// Pad the output to the field width, if needed
|
|
int fieldlen = prefixlen + postprefixzeros + bufflen;
|
|
const char *pad = (flags & F_ZERO) ? zeroes : spaces;
|
|
|
|
// If the output is right aligned and zero-padded, then the prefix must come before the padding.
|
|
if ((flags & (F_ZERO|F_MINUS)) == F_ZERO && prefixlen > 0)
|
|
{
|
|
outlen += output (outputData, prefix, prefixlen);
|
|
prefixlen = 0;
|
|
}
|
|
if (!(flags & F_MINUS) && fieldlen < width)
|
|
{ // Field is right-justified, so padding comes first
|
|
outlen += writepad (output, outputData, pad, sizeof(spaces), width - fieldlen);
|
|
width = -1;
|
|
}
|
|
|
|
// Output field: Prefix, post-prefix zeros, buffer text
|
|
if (prefixlen > 0)
|
|
{
|
|
outlen += output (outputData, prefix, prefixlen);
|
|
}
|
|
outlen += writepad (output, outputData, zeroes, sizeof(spaces), postprefixzeros);
|
|
if (!(flags & F_FPT))
|
|
{
|
|
if (bufflen > 0)
|
|
{
|
|
outlen += output (outputData, obuff, bufflen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (expchar == '\0') // %[fF] or sufficiently short %[gG]
|
|
{
|
|
if (expt <= 0)
|
|
{
|
|
outlen += output (outputData, zeroes, 1);
|
|
if (precision != 0 || (flags & F_HASH))
|
|
{
|
|
outlen += output (outputData, decimal_point, 1);
|
|
}
|
|
outlen += writepad (output, outputData, zeroes, sizeof(zeroes), -expt);
|
|
// already handled initial 0's
|
|
precision += expt;
|
|
}
|
|
else
|
|
{
|
|
outlen += printandpad (output, outputData, obuff, dtoaend, expt, zeroes, sizeof(zeroes));
|
|
obuff += expt;
|
|
if (precision || (flags & F_HASH))
|
|
{
|
|
outlen += output (outputData, decimal_point, 1);
|
|
}
|
|
}
|
|
outlen += printandpad (output, outputData, obuff, dtoaend, precision, zeroes, sizeof(zeroes));
|
|
}
|
|
else // %[eE] or sufficiently long %[gG]
|
|
{
|
|
if (precision > 1 || (flags & F_HASH))
|
|
{
|
|
buffer[0] = *obuff++;
|
|
buffer[1] = *decimal_point;
|
|
outlen += output (outputData, buffer, 2);
|
|
outlen += output (outputData, obuff, ndig - 1);
|
|
outlen += writepad (output, outputData, zeroes, sizeof(zeroes), precision - ndig);
|
|
}
|
|
else // XeYY
|
|
{
|
|
outlen += output (outputData, obuff, 1);
|
|
}
|
|
outlen += output (outputData, expstr, expsize);
|
|
}
|
|
}
|
|
|
|
if ((flags & F_MINUS) && fieldlen < width)
|
|
{ // Field is left-justified, so padding comes last
|
|
outlen += writepad (output, outputData, pad, sizeof(spaces), width - fieldlen);
|
|
}
|
|
len += outlen;
|
|
if (dtoaresult != NULL)
|
|
{
|
|
freedtoa(dtoaresult);
|
|
dtoaresult = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int writepad (OutputFunc output, void *outputData, const char *pad, int padsize, int spaceToFill)
|
|
{
|
|
int outlen = 0;
|
|
while (spaceToFill > 0)
|
|
{
|
|
int count = spaceToFill > padsize ? padsize : spaceToFill;
|
|
outlen += output (outputData, pad, count);
|
|
spaceToFill -= count;
|
|
}
|
|
return outlen;
|
|
}
|
|
|
|
static int printandpad (OutputFunc output, void *outputData, const char *p, const char *ep, int len, const char *with, int padsize)
|
|
{
|
|
int outlen = 0;
|
|
int n2 = (int)(ep - p);
|
|
if (n2 > len)
|
|
{
|
|
n2 = len;
|
|
}
|
|
if (n2 > 0)
|
|
{
|
|
outlen = output (outputData, p, n2);
|
|
}
|
|
return outlen + writepad (output, outputData, with, padsize, len - (n2 > 0 ? n2 : 0));
|
|
}
|
|
|
|
static int exponent (char *p0, int exp, int fmtch)
|
|
{
|
|
char *p, *t;
|
|
char expbuf[MAXEXPDIG];
|
|
|
|
p = p0;
|
|
*p++ = fmtch;
|
|
if (exp < 0)
|
|
{
|
|
exp = -exp;
|
|
*p++ = '-';
|
|
}
|
|
else
|
|
{
|
|
*p++ = '+';
|
|
}
|
|
t = expbuf + MAXEXPDIG;
|
|
if (exp > 9)
|
|
{
|
|
do
|
|
{
|
|
*--t = '0' + (exp % 10);
|
|
}
|
|
while ((exp /= 10) > 9);
|
|
*--t = '0' + exp;
|
|
for(; t < expbuf + MAXEXPDIG; *p++ = *t++)
|
|
{ }
|
|
}
|
|
else
|
|
{
|
|
// Exponents for decimal floating point conversions
|
|
// (%[eEgG]) must be at least two characters long,
|
|
// whereas exponents for hexadecimal conversions can
|
|
// be only one character long.
|
|
if (fmtch == 'e' || fmtch == 'E')
|
|
{
|
|
*p++ = '0';
|
|
}
|
|
*p++ = '0' + exp;
|
|
}
|
|
return (int)(p - p0);
|
|
}
|
|
};
|
|
|
|
//========================================================================//
|
|
// snprintf / vsnprintf imitations
|
|
|
|
#ifdef __GNUC__
|
|
#define GCCPRINTF(stri,firstargi) __attribute__((format(printf,stri,firstargi)))
|
|
#define GCCFORMAT(stri) __attribute__((format(printf,stri,0)))
|
|
#define GCCNOWARN __attribute__((unused))
|
|
#else
|
|
#define GCCPRINTF(a,b)
|
|
#define GCCFORMAT(a)
|
|
#define GCCNOWARN
|
|
#endif
|
|
|
|
struct snprintf_state
|
|
{
|
|
char *buffer;
|
|
size_t maxlen;
|
|
size_t curlen;
|
|
int ideallen;
|
|
};
|
|
|
|
static int myvsnprintf_helper(void *data, const char *cstr, int cstr_len)
|
|
{
|
|
snprintf_state *state = (snprintf_state *)data;
|
|
|
|
if (INT_MAX - cstr_len < state->ideallen)
|
|
{
|
|
state->ideallen = INT_MAX;
|
|
}
|
|
else
|
|
{
|
|
state->ideallen += cstr_len;
|
|
}
|
|
if (state->curlen + cstr_len > state->maxlen)
|
|
{
|
|
cstr_len = (int)(state->maxlen - state->curlen);
|
|
}
|
|
if (cstr_len > 0)
|
|
{
|
|
memcpy(state->buffer + state->curlen, cstr, cstr_len);
|
|
state->curlen += cstr_len;
|
|
}
|
|
return cstr_len;
|
|
}
|
|
|
|
extern "C"
|
|
{
|
|
|
|
// Unlike the MS CRT function snprintf, this one always writes a terminating
|
|
// null character to the buffer. It also returns the full length of the string
|
|
// that would have been output if the buffer had been large enough. In other
|
|
// words, it follows BSD/Linux rules and not MS rules.
|
|
int myvsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
|
|
{
|
|
size_t originalcount = count;
|
|
if (count != 0)
|
|
{
|
|
count--;
|
|
}
|
|
if (count > INT_MAX)
|
|
{ // This is probably an error. Output nothing.
|
|
originalcount = 0;
|
|
count = 0;
|
|
}
|
|
snprintf_state state = { buffer, count, 0, 0 };
|
|
StringFormat::VWorker(myvsnprintf_helper, &state, format, argptr);
|
|
if (originalcount > 0)
|
|
{
|
|
buffer[state.curlen] = '\0';
|
|
}
|
|
return state.ideallen;
|
|
}
|
|
|
|
int mysnprintf(char *buffer, size_t count, const char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, format);
|
|
int len = myvsnprintf(buffer, count, format, argptr);
|
|
va_end(argptr);
|
|
return len;
|
|
}
|
|
|
|
}
|