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
parent fa29bb3b
...@@ -165,13 +165,14 @@ MRB_API mrb_float mrb_as_float(mrb_state *mrb, mrb_value x); ...@@ -165,13 +165,14 @@ MRB_API mrb_float mrb_as_float(mrb_state *mrb, mrb_value x);
/* internal functions */ /* internal functions */
mrb_float mrb_div_float(mrb_float x, mrb_float y); 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); 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 */ /* obsolete functions; will be removed */
#define mrb_flo_to_fixnum(mrb, val) mrb_float_to_integer(mrb, val) #define mrb_flo_to_fixnum(mrb, val) mrb_float_to_integer(mrb, val)
#define mrb_to_flo(mrb, x) mrb_as_float(mrb, x) #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 */ #endif /* MRB_NO_FLOAT */
......
#include <mruby.h> #include <mruby.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#ifndef MRB_NO_FLOAT #ifndef MRB_NO_FLOAT
#ifdef MRB_USE_FLOAT32 #ifdef MRB_USE_FLOAT32
#define FLO_TO_STR_PREC 7 #define FLO_TO_STR_PREC 7
...@@ -7,451 +9,447 @@ ...@@ -7,451 +9,447 @@
#define FLO_TO_STR_PREC 15 #define FLO_TO_STR_PREC 15
#endif #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.
Copyright (c) 2005-2014 Rich Felker, et al.
Permission is hereby granted, free of charge, to any person obtaining Routine for converting a single-precision
a copy of this software and associated documentation files (the floating point number into a string.
"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 The code in this funcion was inspired from Fred Bayer's pdouble.c.
included in all copies or substantial portions of the Software. Since pdouble.c was released as Public Domain, I'm releasing this
code as public domain as well.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Dave Hylands
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.
*/ The original code can be found in https://github.com/dhylands/format-float
***********************************************************************/
#include <limits.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <ctype.h>
#include <mruby/string.h> /***********************************************************************
struct fmt_args;
typedef void output_func(struct fmt_args *f, const char *s, size_t l);
struct fmt_args {
mrb_state *mrb;
output_func *output;
void *opaque;
};
struct mrb_cstr {
char *buf;
size_t len;
};
#define MAX(a,b) ((a)>(b) ? (a) : (b)) I modified the routine for mruby:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
/* Convenient bit representation for modifier flags, which all fall * support `double`
* within 31 codepoints of the space character. */ * support `#` (alt_form) modifier
#define ALT_FORM (1U<<('#'-' ')) My modifications in this file are also placed in the public domain.
#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) Matz (Yukihiro Matsumoto)
static output_func strcat_value; ***********************************************************************/
static output_func strcat_cstr;
static void #include <math.h>
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 #ifdef MRB_USE_FLOAT32
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) { // 1 sign bit, 8 exponent bits, and 23 mantissa bits.
mrb_state *mrb = f->mrb; // 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
mrb_raise(mrb, E_ARGUMENT_ERROR, "string buffer too small"); #define FLT_DECEXP 32
} #define FLT_ROUND_TO_ONE 0.9999995F
#define FLT_MIN_BUF_SIZE 6 // -9e+99
memcpy(cstr->buf, s, l); #else
cstr->buf += l; // 1 sign bit, 11 exponent bits, and 52 mantissa bits.
cstr->len -= l;
}
static void #define FLT_DECEXP 256
out(struct fmt_args *f, const char *s, size_t l) #define FLT_ROUND_TO_ONE 0.999999999995
{ #define FLT_MIN_BUF_SIZE 7 // -9e+199
f->output(f, s, l);
}
#define PAD_SIZE 256 #endif /* MRB_USE_FLOAT32 */
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] = { static const mrb_float g_pos_pow[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' #ifndef MRB_USE_FLOAT32
1e256, 1e128, 1e64,
#endif
1e32, 1e16, 1e8, 1e4, 1e2, 1e1
}; };
static const mrb_float g_neg_pow[] = {
static char* #ifndef MRB_USE_FLOAT32
fmt_u(uint32_t x, char *s) 1e-256, 1e-128, 1e-64,
{
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)];
#endif #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) * mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign)
{ *
uint32_t big[(LDBL_MANT_DIG+28)/29 + 1 // mantissa expansion * fmt: should be one of 'e', 'E', 'f', 'F', 'g', or 'G'. (|0x80 for '#')
+ (LDBL_MAX_EXP+LDBL_MANT_DIG+28+8)/9]; // exponent expansion * prec: is the precision (as specified in printf)
uint32_t *a, *d, *r, *z; * sign: should be '\0', '+', or ' ' ('\0' is the normal one - only print
uint32_t i; * a sign if ```f``` is negative. Anything else is printed as the
int e2=0, e, j; * sign character for positive nubers.
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;
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;
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);
}
y = frexp((double)y, &e2) * 2;
if (y) e2--;
if ((t|32)=='a') { int
long double round = 8.0; mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign) {
ptrdiff_t re; char *s = buf;
int buf_remaining = buf_size - 1;
int alt_form = 0;
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 (t&32) prefix += 9; if (buf_size >= 2) {
pl += 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;
}
}
if (p<0 || p>=LDBL_MANT_DIG/4-1) re=0; if (prec < 0) {
else re=LDBL_MANT_DIG/4-1-p; 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 (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 (re) { // Build negative exponent
while (re--) round*=16; for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*prefix=='-') { if (*neg_pow > f) {
y=-y; e += e1;
y-=round; f *= *pos_pow;
y+=round;
y=-y;
} }
else { }
y+=round; char e_sign_char = '-';
y-=round; if (f < 1.0) {
if (f >= FLT_ROUND_TO_ONE) {
f = 1.0;
if (e == 0) {
e_sign_char = '+';
}
} else {
e++;
f *= 10.0;
} }
} }
estr=fmt_u(e2<0 ? -e2 : e2, ebuf); // If the user specified 'g' format, and e is <= 4, then we'll switch
if (estr==ebuf) *--estr='0'; // to the fixed format ('f')
*--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; if (fmt == 'f' || (fmt == 'g' && e <= 4)) {
else a=r=z=big+sizeof(big)/sizeof(*big) - LDBL_MANT_DIG - 1; fmt = 'f';
dec = -1;
*s++ = first_dig;
do { if (prec + e + 1 > buf_remaining) {
*z = (uint32_t)y; prec = buf_remaining - e - 1;
y = 1000000000*(y-*z++); }
} while (y);
while (e2>0) { if (org_fmt == 'g') {
uint32_t carry=0; prec += (e - 1);
int sh=MIN(29,e2); }
for (d=z-1; d>=a; d--) { num_digits = prec;
uint64_t x = ((uint64_t)*d<<sh)+carry; if (num_digits || alt_form) {
*d = x % 1000000000; *s++ = '.';
carry = (uint32_t)(x / 1000000000); 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++;
}
}
} }
if (carry) *--a = carry; } else {
while (z>a && !z[-1]) z--; // Build positive exponent
e2-=sh; for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
} if (*pos_pow <= f) {
while (e2<0) { e += e1;
uint32_t carry=0, *b; f *= *neg_pow;
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++); // If the user specified fixed format (fmt == 'f') and e makes the
else e=0; // number too big to fit into the available buffer, then we'll
// switch to the 'e' format.
/* Perform rounding: j is precision after the radix (possibly neg) */
j = (int)p - ((t|32)!='f')*e - ((t|32)=='g' && p); if (fmt == 'f') {
if (j < 9*(z-r-1)) { if (e >= buf_remaining) {
uint32_t x; fmt = 'e';
/* We avoid C's broken division of negative numbers */ } else if ((e + prec + 2) > buf_remaining) {
d = r + 1 + ((j+9*LDBL_MAX_EXP)/9 - LDBL_MAX_EXP); prec = buf_remaining - e - 2;
j += 9*LDBL_MAX_EXP; if (prec < 0) {
j %= 9; // This means no decimal point, so we can add one back
for (i=10, j++; j<9; i*=10, j++); // for the decimal.
x = *d % i; prec++;
/* 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)++;
} }
for (i=10, e=9*(int)(r-a); *a>=i; i*=10, e++);
} }
} }
if (z>d+1) z=d+1; if (fmt == 'e' && prec > (buf_remaining - 6)) {
} prec = buf_remaining - 6;
for (; z>a && !z[-1]; z--);
if ((t|32)=='g') {
if (!p) p++;
if (p>e && e>=-4) {
t--;
p-=e+1;
} }
else { // If the user specified 'g' format, and e is < prec, then we'll switch
t-=2; // to the fixed format.
p--;
if (fmt == 'g' && e < prec) {
fmt = 'f';
prec -= (e + 1);
} }
if (!(fl&ALT_FORM)) { if (fmt == 'f') {
/* Count trailing zeros in last place */ dec = e;
if (z>a && z[-1]) for (i=10, j=0; z[-1]%i==0; i*=10, j++); num_digits = prec + e + 1;
else j=9; } else {
if ((t|32)=='f') e_sign = '+';
p = MIN(p,MAX(0,9*(z-r-1)-j));
else
p = MIN(p,MAX(0,9*(z-r-1)+e-j));
} }
} }
l = 1 + p + (p || (fl&ALT_FORM)); if (prec < 0) {
if ((t|32)=='f') { // This can happen when the prec is trimmed to prevent buffer overflow
if (e>0) l+=e; prec = 0;
}
else {
estr=fmt_u(e<0 ? -e : e, ebuf);
while(ebuf-estr<2) *--estr='0';
*--estr = (e<0 ? '-' : '+');
*--estr = t;
l += ebuf-estr;
} }
pad(f, ' ', w, pl+l, fl); // We now have f as a floating point number between >= 1 and < 10
out(f, prefix, pl); // (or equal to zero), and e contains the absolute value of the power of
pad(f, '0', w, pl+l, fl^ZERO_PAD); // 10 exponent. and (dec + 1) == the number of dgits before the decimal.
if ((t|32)=='f') { // For e, prec is # digits after the decimal
if (a>r) a=r; // For f, prec is # digits after the decimal
for (d=a; d<=r; d++) { // For g, prec is the max number of significant digits
char *ss = fmt_u(*d, buf+9); //
if (d!=a) while (ss>buf) *--ss='0'; // For e & g there will be a single digit before the decimal
else if (ss==buf+9) *--ss='0'; // for f there will be e digits before the decimal
out(f, ss, buf+9-ss);
if (fmt == 'e') {
num_digits = prec + 1;
} else if (fmt == 'g') {
if (prec == 0) {
prec = 1;
} }
if (p || (fl&ALT_FORM)) out(f, ".", 1); num_digits = prec;
for (; d<z && p>0; d++, p-=9) { }
char *ss = fmt_u(*d, buf+9);
while (ss>buf) *--ss='0'; // Print the digits of the mantissa
out(f, ss, MIN(9,p)); for (int i = 0; i < num_digits; ++i, --dec) {
int8_t d = f;
*s++ = '0' + d;
if (dec == 0 && (prec > 0 || alt_form)) {
*s++ = '.';
} }
pad(f, '0', p+9, 9, 0); f -= (mrb_float)d;
f *= 10.0;
} }
else {
if (z<=a) z=a+1; // Round
for (d=a; d<z && p>=0; d++) { if (f >= 5.0) {
char *ss = fmt_u(*d, buf+9); char *rs = s;
if (ss==buf+9) *--ss='0'; rs--;
if (d!=a) while (ss>buf) *--ss='0'; while (1) {
else { if (*rs == '.') {
out(f, ss++, 1); rs--;
if (p>0||(fl&ALT_FORM)) out(f, ".", 1); 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--;
} }
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);
} }
pad(f, ' ', w, pl+l, fl^LEFT_ADJ); 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 (int)MAX(w, pl+l); return s - buf;
} }
static int
fmt_core(struct fmt_args *f, const char *fmt, mrb_float flo)
{
ptrdiff_t w, p;
uint32_t fl;
if (*fmt != '%') { int
return -1; 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++;
} }
++fmt; if (ISDIGIT(*s)) {
char *endptr;
/* Read modifier flags */ width = strtoul(s, &endptr, 10);
for (fl=0; (unsigned)*fmt-' '<32 && (FLAGMASK&(1U<<(*fmt-' '))); fmt++) s = endptr;
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 (*s == '.') {
if (*fmt == '.') { s++;
++fmt; if (ISDIGIT(*s)) {
for (p = 0; ISDIGIT(*fmt); ++fmt) { char *endptr;
p = 10 * p + (*fmt - '0'); prec = strtoul(s, &endptr, 10);
s = endptr;
}
else {
prec = 0;
} }
} }
else { char c = *s;
p = -1; if (alt_form) {
c |= 0x80;
} }
int len = mrb_format_float(f, buf, buf_size, c, prec, sign);
switch (*fmt) {
case 'e': case 'f': case 'g': case 'a': // buf[0] < '0' returns true if the first character is space, + or -
case 'E': case 'F': case 'G': case 'A': // buf[1] < '9' matches a digit, and doesn't match when we get back +nan or +inf
return fmt_fp(f, flo, w, p, fl, *fmt); if (buf[0] < '0' && buf[1] <= '9' && zero_pad) {
default: buf++;
return -1; width--;
len--;
} }
} if (*buf < '0' || *buf >= '9') {
// For inf or nan, we don't want to zero pad.
int zero_pad = 0;
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'; if (len >= width) {
return (int)(cstr.buf - buf); return len;
} }
#else /* MRB_NO_STDIO || _WIN32 || _WIN64 */ buf[width] = '\0';
#include <stdio.h> if (left_align) {
memset(&buf[len], ' ', width - len);
int return width;
mrb_float_to_cstr(mrb_state *mrb, char *buf, size_t len, const char *fmt, mrb_float fval) }
{ memmove(&buf[width - len], buf, len);
return snprintf(buf, len, fmt, fval); if (zero_pad) {
memset(buf, '0', width - len);
} else {
memset(buf, ' ', width - len);
}
return width;
} }
#endif /* MRB_NO_STDIO || _WIN32 || _WIN64 */
MRB_API mrb_value MRB_API mrb_value
mrb_float_to_str(mrb_state *mrb, mrb_value flo) mrb_float_to_str(mrb_state *mrb, mrb_value flo)
{ {
char fmt[] = "%." MRB_STRINGIZE(FLO_TO_STR_PREC) "g";
char buf[25]; 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++) { for (char *p = buf; *p; p++) {
if (*p == '.') goto exit; if (*p == '.') goto exit;
if (*p == 'e') { if (*p == 'e') {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment