fmt_fp.c: replace with public domain float format routine; ref #5448

The original code can be found in `https://github.com/dhylands/format-float`.

Changes:
- support `double`
- support `#` (alt_form) modifier
- small refactoring
This commit is contained in:
Yukihiro "Matz" Matsumoto
2021-05-21 07:37:13 +09:00
parent fa29bb3bb4
commit 328563995f
2 changed files with 401 additions and 402 deletions
+3 -2
View File
@@ -165,13 +165,14 @@ MRB_API mrb_float mrb_as_float(mrb_state *mrb, mrb_value x);
/* internal functions */
mrb_float mrb_div_float(mrb_float x, mrb_float y);
/* ArgumentError if format string doesn't match /%(\.[0-9]+)?[aAeEfFgG]/ */
mrb_value mrb_float_to_str(mrb_state *mrb, mrb_value x);
int mrb_float_to_cstr(mrb_state *mrb, char *buf, size_t len, const char *fmt, mrb_float f);
int mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign);
/* obsolete functions; will be removed */
#define mrb_flo_to_fixnum(mrb, val) mrb_float_to_integer(mrb, val)
#define mrb_to_flo(mrb, x) mrb_as_float(mrb, x)
/* ArgumentError if format string doesn't match /%(\.[0-9]+)?[aAeEfFgG]/ */
int mrb_float_to_cstr(mrb_state *mrb, char *buf, size_t len, const char *fmt, mrb_float f);
#endif /* MRB_NO_FLOAT */
+398 -400
View File
@@ -1,5 +1,7 @@
#include <mruby.h>
#include <string.h>
#include <stdlib.h>
#ifndef MRB_NO_FLOAT
#ifdef MRB_USE_FLOAT32
#define FLO_TO_STR_PREC 7
@@ -7,451 +9,447 @@
#define FLO_TO_STR_PREC 15
#endif
#if defined(MRB_NO_STDIO) || defined(_WIN32) || defined(_WIN64)
/*
/***********************************************************************
Most code in this file originates from musl (src/stdio/vfprintf.c)
which, just like mruby itself, is licensed under the MIT license.
Routine for converting a single-precision
floating point number into a string.
Copyright (c) 2005-2014 Rich Felker, et al.
The code in this funcion was inspired from Fred Bayer's pdouble.c.
Since pdouble.c was released as Public Domain, I'm releasing this
code as public domain as well.
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:
Dave Hylands
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
The original code can be found in https://github.com/dhylands/format-float
***********************************************************************/
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.
/***********************************************************************
*/
I modified the routine for mruby:
* support `double`
* support `#` (alt_form) modifier
My modifications in this file are also placed in the public domain.
Matz (Yukihiro Matsumoto)
***********************************************************************/
#include <limits.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <ctype.h>
#include <mruby/string.h>
#ifdef MRB_USE_FLOAT32
struct fmt_args;
// 1 sign bit, 8 exponent bits, and 23 mantissa bits.
// exponent values 0 and 255 are reserved, exponent can be 1 to 254.
// exponent is stored with a bias of 127.
// The min and max floats are on the order of 1x10^37 and 1x10^-37
typedef void output_func(struct fmt_args *f, const char *s, size_t l);
#define FLT_DECEXP 32
#define FLT_ROUND_TO_ONE 0.9999995F
#define FLT_MIN_BUF_SIZE 6 // -9e+99
struct fmt_args {
mrb_state *mrb;
output_func *output;
void *opaque;
};
#else
struct mrb_cstr {
char *buf;
size_t len;
};
// 1 sign bit, 11 exponent bits, and 52 mantissa bits.
#define MAX(a,b) ((a)>(b) ? (a) : (b))
#define MIN(a,b) ((a)<(b) ? (a) : (b))
#define FLT_DECEXP 256
#define FLT_ROUND_TO_ONE 0.999999999995
#define FLT_MIN_BUF_SIZE 7 // -9e+199
/* Convenient bit representation for modifier flags, which all fall
* within 31 codepoints of the space character. */
#endif /* MRB_USE_FLOAT32 */
#define ALT_FORM (1U<<('#'-' '))
#define ZERO_PAD (1U<<('0'-' '))
#define LEFT_ADJ (1U<<('-'-' '))
#define PAD_POS (1U<<(' '-' '))
#define MARK_POS (1U<<('+'-' '))
#define FLAGMASK (ALT_FORM|ZERO_PAD|LEFT_ADJ|PAD_POS|MARK_POS)
static output_func strcat_value;
static output_func strcat_cstr;
static void
strcat_value(struct fmt_args *f, const char *s, size_t l)
{
mrb_value str = *(mrb_value*)f->opaque;
mrb_str_cat(f->mrb, str, s, l);
}
static void
strcat_cstr(struct fmt_args *f, const char *s, size_t l)
{
struct mrb_cstr *cstr = (struct mrb_cstr*)f->opaque;
if (l > cstr->len) {
mrb_state *mrb = f->mrb;
mrb_raise(mrb, E_ARGUMENT_ERROR, "string buffer too small");
}
memcpy(cstr->buf, s, l);
cstr->buf += l;
cstr->len -= l;
}
static void
out(struct fmt_args *f, const char *s, size_t l)
{
f->output(f, s, l);
}
#define PAD_SIZE 256
static void
pad(struct fmt_args *f, char c, ptrdiff_t w, ptrdiff_t l, uint32_t fl)
{
char pad[PAD_SIZE];
if (fl & (LEFT_ADJ | ZERO_PAD) || l >= w) return;
l = w - l;
memset(pad, c, l>PAD_SIZE ? PAD_SIZE : l);
for (; l >= PAD_SIZE; l -= PAD_SIZE)
out(f, pad, PAD_SIZE);
out(f, pad, l);
}
static const char xdigits[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
static char*
fmt_u(uint32_t x, char *s)
{
for (; x; x /= 10) *--s = '0' + x % 10;
return s;
}
/* Do not override this check. The floating-point printing code below
* depends on the float.h constants being right. If they are wrong, it
* may overflow the stack. */
#if LDBL_MANT_DIG == 53
typedef char compiler_defines_long_double_incorrectly[9-(int)sizeof(long double)];
static const mrb_float g_pos_pow[] = {
#ifndef MRB_USE_FLOAT32
1e256, 1e128, 1e64,
#endif
1e32, 1e16, 1e8, 1e4, 1e2, 1e1
};
static const mrb_float g_neg_pow[] = {
#ifndef MRB_USE_FLOAT32
1e-256, 1e-128, 1e-64,
#endif
1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1
};
static int
fmt_fp(struct fmt_args *f, long double y, ptrdiff_t w, ptrdiff_t p, uint32_t fl, int t)
{
uint32_t big[(LDBL_MANT_DIG+28)/29 + 1 // mantissa expansion
+ (LDBL_MAX_EXP+LDBL_MANT_DIG+28+8)/9]; // exponent expansion
uint32_t *a, *d, *r, *z;
uint32_t i;
int e2=0, e, j;
ptrdiff_t l;
char buf[9+LDBL_MANT_DIG/4], *s;
const char *prefix="-0X+0X 0X-0x+0x 0x";
ptrdiff_t pl;
char ebuf0[3*sizeof(int)], *ebuf=&ebuf0[3*sizeof(int)], *estr;
/*
* mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign)
*
* fmt: should be one of 'e', 'E', 'f', 'F', 'g', or 'G'. (|0x80 for '#')
* prec: is the precision (as specified in printf)
* sign: should be '\0', '+', or ' ' ('\0' is the normal one - only print
* a sign if ```f``` is negative. Anything else is printed as the
* sign character for positive nubers.
*/
pl=1;
if (signbit(y)) {
y=-y;
} else if (fl & MARK_POS) {
prefix+=3;
} else if (fl & PAD_POS) {
prefix+=6;
} else prefix++, pl=0;
int
mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign) {
char *s = buf;
int buf_remaining = buf_size - 1;
int alt_form = 0;
if (!isfinite(y)) {
const char *ss = (t&32)?"inf":"INF";
if (y!=y) ss=(t&32)?"nan":"NAN";
pad(f, ' ', w, 3+pl, fl&~ZERO_PAD);
out(f, prefix, pl);
out(f, ss, 3);
pad(f, ' ', w, 3+pl, fl^LEFT_ADJ);
return (int)MAX(w, 3+pl);
if ((uint8_t)fmt & 0x80) {
fmt &= 0x7f; /* turn off alt_form flag */
alt_form = 1;
}
if (buf_size <= FLT_MIN_BUF_SIZE) {
// Smallest exp notion is -9e+99 (-9e+199) which is 6 (7) chars plus terminating
// null.
if (buf_size >= 2) {
*s++ = '?';
}
if (buf_size >= 1) {
*s++ = '\0';
}
return buf_size >= 2;
}
if (signbit(f)) {
*s++ = '-';
f = -f;
} else if (sign) {
*s++ = sign;
}
buf_remaining -= (s - buf); // Adjust for sign
{
char uc = fmt & 0x20;
if (isinf(f)) {
*s++ = 'I' ^ uc;
*s++ = 'N' ^ uc;
*s++ = 'F' ^ uc;
goto ret;
} else if (isnan(f)) {
*s++ = 'N' ^ uc;
*s++ = 'A' ^ uc;
*s++ = 'N' ^ uc;
ret:
*s = '\0';
return s - buf;
}
}
y = frexp((double)y, &e2) * 2;
if (y) e2--;
if (prec < 0) {
prec = 6;
}
char e_char = 'E' | (fmt & 0x20); // e_char will match case of fmt
fmt |= 0x20; // Force fmt to be lowercase
char org_fmt = fmt;
if (fmt == 'g' && prec == 0) {
prec = 1;
}
int e, e1;
int dec = 0;
char e_sign = '\0';
int num_digits = 0;
const mrb_float *pos_pow = g_pos_pow;
const mrb_float *neg_pow = g_neg_pow;
if ((t|32)=='a') {
long double round = 8.0;
ptrdiff_t re;
if (f == 0.0) {
e = 0;
if (fmt == 'e') {
e_sign = '+';
} else if (fmt == 'f') {
num_digits = prec + 1;
}
} else if (f < 1.0) { // f < 1.0
char first_dig = '0';
if (f >= FLT_ROUND_TO_ONE) {
first_dig = '1';
}
if (t&32) prefix += 9;
pl += 2;
if (p<0 || p>=LDBL_MANT_DIG/4-1) re=0;
else re=LDBL_MANT_DIG/4-1-p;
if (re) {
while (re--) round*=16;
if (*prefix=='-') {
y=-y;
y-=round;
y+=round;
y=-y;
}
else {
y+=round;
y-=round;
// Build negative exponent
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*neg_pow > f) {
e += e1;
f *= *pos_pow;
}
}
estr=fmt_u(e2<0 ? -e2 : e2, ebuf);
if (estr==ebuf) *--estr='0';
*--estr = (e2<0 ? '-' : '+');
*--estr = t+('p'-'a');
s=buf;
do {
int x=(int)y;
*s++=xdigits[x]|(t&32);
y=16*(y-x);
if (s-buf==1 && (y||p>0||(fl&ALT_FORM))) *s++='.';
} while (y);
if (p && s-buf-2 < p)
l = (p+2) + (ebuf-estr);
else
l = (s-buf) + (ebuf-estr);
pad(f, ' ', w, pl+l, fl);
out(f, prefix, pl);
pad(f, '0', w, pl+l, fl^ZERO_PAD);
out(f, buf, s-buf);
pad(f, '0', l-(ebuf-estr)-(s-buf), 0, 0);
out(f, estr, ebuf-estr);
pad(f, ' ', w, pl+l, fl^LEFT_ADJ);
return (int)MAX(w, pl+l);
}
if (p<0) p=6;
if (y) y *= 268435456.0, e2-=28;
if (e2<0) a=r=z=big;
else a=r=z=big+sizeof(big)/sizeof(*big) - LDBL_MANT_DIG - 1;
do {
*z = (uint32_t)y;
y = 1000000000*(y-*z++);
} while (y);
while (e2>0) {
uint32_t carry=0;
int sh=MIN(29,e2);
for (d=z-1; d>=a; d--) {
uint64_t x = ((uint64_t)*d<<sh)+carry;
*d = x % 1000000000;
carry = (uint32_t)(x / 1000000000);
}
if (carry) *--a = carry;
while (z>a && !z[-1]) z--;
e2-=sh;
}
while (e2<0) {
uint32_t carry=0, *b;
int sh=MIN(9,-e2), need=1+((int)p+LDBL_MANT_DIG/3+8)/9;
for (d=a; d<z; d++) {
uint32_t rm = *d & ((1<<sh)-1);
*d = (*d>>sh) + carry;
carry = (1000000000>>sh) * rm;
}
if (!*a) a++;
if (carry) *z++ = carry;
/* Avoid (slow!) computation past requested precision */
b = (t|32)=='f' ? r : a;
if (z-b > need) z = b+need;
e2+=sh;
}
if (a<z) for (i=10, e=9*(int)(r-a); *a>=i; i*=10, e++);
else e=0;
/* Perform rounding: j is precision after the radix (possibly neg) */
j = (int)p - ((t|32)!='f')*e - ((t|32)=='g' && p);
if (j < 9*(z-r-1)) {
uint32_t x;
/* We avoid C's broken division of negative numbers */
d = r + 1 + ((j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP);
j += 9*LDBL_MAX_EXP;
j %= 9;
for (i=10, j++; j<9; i*=10, j++);
x = *d % i;
/* Are there any significant digits past j? */
if (x || d+1!=z) {
long double round = 2/LDBL_EPSILON;
long double small;
if (*d/i & 1) round += 2;
if (x<i/2) small=0.5;
else if (x==i/2 && d+1==z) small=1.0;
else small=1.5;
if (pl && *prefix=='-') round*=-1, small*=-1;
*d -= x;
/* Decide whether to round by probing round+small */
if (round+small != round) {
*d = *d + i;
while (*d > 999999999) {
*d--=0;
if (d<a) *--a=0;
(*d)++;
char e_sign_char = '-';
if (f < 1.0) {
if (f >= FLT_ROUND_TO_ONE) {
f = 1.0;
if (e == 0) {
e_sign_char = '+';
}
for (i=10, e=9*(int)(r-a); *a>=i; i*=10, e++);
} else {
e++;
f *= 10.0;
}
}
if (z>d+1) z=d+1;
}
for (; z>a && !z[-1]; z--);
if ((t|32)=='g') {
if (!p) p++;
if (p>e && e>=-4) {
t--;
p-=e+1;
// If the user specified 'g' format, and e is <= 4, then we'll switch
// to the fixed format ('f')
if (fmt == 'f' || (fmt == 'g' && e <= 4)) {
fmt = 'f';
dec = -1;
*s++ = first_dig;
if (prec + e + 1 > buf_remaining) {
prec = buf_remaining - e - 1;
}
if (org_fmt == 'g') {
prec += (e - 1);
}
num_digits = prec;
if (num_digits || alt_form) {
*s++ = '.';
while (--e && num_digits) {
*s++ = '0';
num_digits--;
}
}
} else {
// For e & g formats, we'll be printing the exponent, so set the
// sign.
e_sign = e_sign_char;
dec = 0;
if (prec > (buf_remaining - 6)) {
prec = buf_remaining - 6;
if (fmt == 'g') {
prec++;
}
}
}
} else {
// Build positive exponent
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*pos_pow <= f) {
e += e1;
f *= *neg_pow;
}
}
// If the user specified fixed format (fmt == 'f') and e makes the
// number too big to fit into the available buffer, then we'll
// switch to the 'e' format.
if (fmt == 'f') {
if (e >= buf_remaining) {
fmt = 'e';
} else if ((e + prec + 2) > buf_remaining) {
prec = buf_remaining - e - 2;
if (prec < 0) {
// This means no decimal point, so we can add one back
// for the decimal.
prec++;
}
}
}
if (fmt == 'e' && prec > (buf_remaining - 6)) {
prec = buf_remaining - 6;
}
// If the user specified 'g' format, and e is < prec, then we'll switch
// to the fixed format.
if (fmt == 'g' && e < prec) {
fmt = 'f';
prec -= (e + 1);
}
if (fmt == 'f') {
dec = e;
num_digits = prec + e + 1;
} else {
e_sign = '+';
}
}
if (prec < 0) {
// This can happen when the prec is trimmed to prevent buffer overflow
prec = 0;
}
// We now have f as a floating point number between >= 1 and < 10
// (or equal to zero), and e contains the absolute value of the power of
// 10 exponent. and (dec + 1) == the number of dgits before the decimal.
// For e, prec is # digits after the decimal
// For f, prec is # digits after the decimal
// For g, prec is the max number of significant digits
//
// For e & g there will be a single digit before the decimal
// for f there will be e digits before the decimal
if (fmt == 'e') {
num_digits = prec + 1;
} else if (fmt == 'g') {
if (prec == 0) {
prec = 1;
}
num_digits = prec;
}
// Print the digits of the mantissa
for (int i = 0; i < num_digits; ++i, --dec) {
int8_t d = f;
*s++ = '0' + d;
if (dec == 0 && (prec > 0 || alt_form)) {
*s++ = '.';
}
f -= (mrb_float)d;
f *= 10.0;
}
// Round
if (f >= 5.0) {
char *rs = s;
rs--;
while (1) {
if (*rs == '.') {
rs--;
continue;
}
if (*rs < '0' || *rs > '9') {
// + or -
rs++; // So we sit on the digit to the right of the sign
break;
}
if (*rs < '9') {
(*rs)++;
break;
}
*rs = '0';
if (rs == buf) {
break;
}
rs--;
}
if (*rs == '0') {
// We need to insert a 1
if (rs[1] == '.' && fmt != 'f') {
// We're going to round 9.99 to 10.00
// Move the decimal point
rs[0] = '.';
rs[1] = '0';
if (e_sign == '-') {
e--;
} else {
e++;
}
}
s++;
char *ss = s;
while (ss > rs) {
*ss = ss[-1];
ss--;
}
*rs = '1';
if (f < 1.0 && fmt == 'f') {
// We rounded up to 1.0
prec--;
}
}
}
if (org_fmt == 'g' && prec > 0 && !alt_form) {
// Remove trailing zeros and a trailing decimal point
while (s[-1] == '0') {
s--;
}
if (s[-1] == '.') {
s--;
}
}
// Append the exponent
if (e_sign) {
*s++ = e_char;
*s++ = e_sign;
*s++ = '0' + (e / 10);
*s++ = '0' + (e % 10);
}
*s = '\0';
return s - buf;
}
int
mrb_float_to_cstr(mrb_state *mrb, char *buf, size_t buf_size, const char *fmt, mrb_float f)
{
const char *s = fmt;
char sign = '\0';
int alt_form = 0;
int left_align = 0;
int zero_pad = 0;
int width = -1;
int prec = 6;
s++; // skip %
while (*s) {
if (*s == '-') {
left_align = 1;
} else if (*s == '+') {
sign = '+';
} else if (*s == ' ') {
sign = ' ';
} else if (*s == '0') {
zero_pad = 1;
} else if (*s == '#') {
alt_form = 1;
} else {
break;
}
s++;
}
if (ISDIGIT(*s)) {
char *endptr;
width = strtoul(s, &endptr, 10);
s = endptr;
}
if (*s == '.') {
s++;
if (ISDIGIT(*s)) {
char *endptr;
prec = strtoul(s, &endptr, 10);
s = endptr;
}
else {
t-=2;
p--;
}
if (!(fl&ALT_FORM)) {
/* Count trailing zeros in last place */
if (z>a && z[-1]) for (i=10, j=0; z[-1]%i==0; i*=10, j++);
else j=9;
if ((t|32)=='f')
p = MIN(p,MAX(0,9*(z-r-1)-j));
else
p = MIN(p,MAX(0,9*(z-r-1)+e-j));
prec = 0;
}
}
l = 1 + p + (p || (fl&ALT_FORM));
if ((t|32)=='f') {
if (e>0) l+=e;
}
else {
estr=fmt_u(e<0 ? -e : e, ebuf);
while(ebuf-estr<2) *--estr='0';
*--estr = (e<0 ? '-' : '+');
*--estr = t;
l += ebuf-estr;
char c = *s;
if (alt_form) {
c |= 0x80;
}
int len = mrb_format_float(f, buf, buf_size, c, prec, sign);
pad(f, ' ', w, pl+l, fl);
out(f, prefix, pl);
pad(f, '0', w, pl+l, fl^ZERO_PAD);
if ((t|32)=='f') {
if (a>r) a=r;
for (d=a; d<=r; d++) {
char *ss = fmt_u(*d, buf+9);
if (d!=a) while (ss>buf) *--ss='0';
else if (ss==buf+9) *--ss='0';
out(f, ss, buf+9-ss);
}
if (p || (fl&ALT_FORM)) out(f, ".", 1);
for (; d<z && p>0; d++, p-=9) {
char *ss = fmt_u(*d, buf+9);
while (ss>buf) *--ss='0';
out(f, ss, MIN(9,p));
}
pad(f, '0', p+9, 9, 0);
// buf[0] < '0' returns true if the first character is space, + or -
// buf[1] < '9' matches a digit, and doesn't match when we get back +nan or +inf
if (buf[0] < '0' && buf[1] <= '9' && zero_pad) {
buf++;
width--;
len--;
}
else {
if (z<=a) z=a+1;
for (d=a; d<z && p>=0; d++) {
char *ss = fmt_u(*d, buf+9);
if (ss==buf+9) *--ss='0';
if (d!=a) while (ss>buf) *--ss='0';
else {
out(f, ss++, 1);
if (p>0||(fl&ALT_FORM)) out(f, ".", 1);
}
out(f, ss, MIN(buf+9-ss, p));
p -= (int)(buf+9-ss);
}
pad(f, '0', p+18, 18, 0);
out(f, estr, ebuf-estr);
if (*buf < '0' || *buf >= '9') {
// For inf or nan, we don't want to zero pad.
zero_pad = 0;
}
pad(f, ' ', w, pl+l, fl^LEFT_ADJ);
return (int)MAX(w, pl+l);
if (len >= width) {
return len;
}
buf[width] = '\0';
if (left_align) {
memset(&buf[len], ' ', width - len);
return width;
}
memmove(&buf[width - len], buf, len);
if (zero_pad) {
memset(buf, '0', width - len);
} else {
memset(buf, ' ', width - len);
}
return width;
}
static int
fmt_core(struct fmt_args *f, const char *fmt, mrb_float flo)
{
ptrdiff_t w, p;
uint32_t fl;
if (*fmt != '%') {
return -1;
}
++fmt;
/* Read modifier flags */
for (fl=0; (unsigned)*fmt-' '<32 && (FLAGMASK&(1U<<(*fmt-' '))); fmt++)
fl |= 1U<<(*fmt-' ');
/* - and 0 flags are mutually exclusive */
if (fl & LEFT_ADJ) fl &= ~ZERO_PAD;
for (w = 0; ISDIGIT(*fmt); ++fmt) {
w = 10 * w + (*fmt - '0');
}
if (*fmt == '.') {
++fmt;
for (p = 0; ISDIGIT(*fmt); ++fmt) {
p = 10 * p + (*fmt - '0');
}
}
else {
p = -1;
}
switch (*fmt) {
case 'e': case 'f': case 'g': case 'a':
case 'E': case 'F': case 'G': case 'A':
return fmt_fp(f, flo, w, p, fl, *fmt);
default:
return -1;
}
}
int
mrb_float_to_cstr(mrb_state *mrb, char *buf, size_t len, const char *fmt, mrb_float fval)
{
struct fmt_args f;
struct mrb_cstr cstr;
cstr.buf = buf;
cstr.len = len - 1; /* reserve NUL terminator */
f.mrb = mrb;
f.output = strcat_cstr;
f.opaque = (void*)&cstr;
if (fmt_core(&f, fmt, fval) < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format string");
}
*cstr.buf = '\0';
return (int)(cstr.buf - buf);
}
#else /* MRB_NO_STDIO || _WIN32 || _WIN64 */
#include <stdio.h>
int
mrb_float_to_cstr(mrb_state *mrb, char *buf, size_t len, const char *fmt, mrb_float fval)
{
return snprintf(buf, len, fmt, fval);
}
#endif /* MRB_NO_STDIO || _WIN32 || _WIN64 */
MRB_API mrb_value
mrb_float_to_str(mrb_state *mrb, mrb_value flo)
{
char fmt[] = "%." MRB_STRINGIZE(FLO_TO_STR_PREC) "g";
char buf[25];
mrb_float_to_cstr(mrb, buf, sizeof(buf), fmt, mrb_float(flo));
mrb_format_float(mrb_float(flo), buf, sizeof(buf), 'g', FLO_TO_STR_PREC, '\0');
for (char *p = buf; *p; p++) {
if (*p == '.') goto exit;
if (*p == 'e') {