summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorYukihiro "Matz" Matsumoto <[email protected]>2021-05-21 07:37:13 +0900
committerYukihiro "Matz" Matsumoto <[email protected]>2021-05-22 09:35:27 +0900
commit328563995f7c817fc4ec1e92ffcadfde101ced06 (patch)
tree8c3b942eb5fc919bfa48c25018d969dd7eeeb801 /src
parentfa29bb3bb4801c8ca0256bcd2559ceb159110f68 (diff)
downloadmruby-328563995f7c817fc4ec1e92ffcadfde101ced06.tar.gz
mruby-328563995f7c817fc4ec1e92ffcadfde101ced06.zip
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
Diffstat (limited to 'src')
-rw-r--r--src/fmt_fp.c748
1 files changed, 373 insertions, 375 deletions
diff --git a/src/fmt_fp.c b/src/fmt_fp.c
index 788b2a70e..50abe3747 100644
--- a/src/fmt_fp.c
+++ b/src/fmt_fp.c
@@ -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.
-
-Copyright (c) 2005-2014 Rich Felker, et al.
+/***********************************************************************
-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:
+ Routine for converting a single-precision
+ floating point number into a string.
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+ 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.
-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.
+ Dave Hylands
-*/
-
-#include <limits.h>
-#include <string.h>
-#include <math.h>
-#include <float.h>
-#include <ctype.h>
+ The original code can be found in https://github.com/dhylands/format-float
+***********************************************************************/
-#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))
-#define MIN(a,b) ((a)<(b) ? (a) : (b))
+ I modified the routine for mruby:
-/* Convenient bit representation for modifier flags, which all fall
- * within 31 codepoints of the space character. */
+ * support `double`
+ * support `#` (alt_form) modifier
-#define ALT_FORM (1U<<('#'-' '))
-#define ZERO_PAD (1U<<('0'-' '))
-#define LEFT_ADJ (1U<<('-'-' '))
-#define PAD_POS (1U<<(' '-' '))
-#define MARK_POS (1U<<('+'-' '))
+ My modifications in this file are also placed in the public domain.
-#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
-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);
-}
+#include <math.h>
-static void
-strcat_cstr(struct fmt_args *f, const char *s, size_t l)
-{
- struct mrb_cstr *cstr = (struct mrb_cstr*)f->opaque;
+#ifdef MRB_USE_FLOAT32
- if (l > cstr->len) {
- mrb_state *mrb = f->mrb;
+// 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
- 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;
- cstr->len -= l;
-}
+// 1 sign bit, 11 exponent bits, and 52 mantissa bits.
-static void
-out(struct fmt_args *f, const char *s, size_t l)
-{
- f->output(f, s, l);
-}
+#define FLT_DECEXP 256
+#define FLT_ROUND_TO_ONE 0.999999999995
+#define FLT_MIN_BUF_SIZE 7 // -9e+199
-#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);
-}
+#endif /* MRB_USE_FLOAT32 */
-static const char xdigits[16] = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+static const mrb_float g_pos_pow[] = {
+#ifndef MRB_USE_FLOAT32
+ 1e256, 1e128, 1e64,
+#endif
+ 1e32, 1e16, 1e8, 1e4, 1e2, 1e1
};
-
-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_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;
-
- 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--;
+/*
+ * 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.
+ */
- if ((t|32)=='a') {
- long double round = 8.0;
- ptrdiff_t re;
+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 ((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;
- pl += 2;
+ 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;
+ }
+ }
- if (p<0 || p>=LDBL_MANT_DIG/4-1) re=0;
- else re=LDBL_MANT_DIG/4-1-p;
+ 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 (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) {
- while (re--) round*=16;
- if (*prefix=='-') {
- y=-y;
- y-=round;
- y+=round;
- y=-y;
+ // 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;
}
- else {
- y+=round;
- y-=round;
+ }
+ char e_sign_char = '-';
+ 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 (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 the user specified 'g' format, and e is <= 4, then we'll switch
+ // to the fixed format ('f')
- if (e2<0) a=r=z=big;
- else a=r=z=big+sizeof(big)/sizeof(*big) - LDBL_MANT_DIG - 1;
+ if (fmt == 'f' || (fmt == 'g' && e <= 4)) {
+ fmt = 'f';
+ dec = -1;
+ *s++ = first_dig;
- do {
- *z = (uint32_t)y;
- y = 1000000000*(y-*z++);
- } while (y);
+ if (prec + e + 1 > buf_remaining) {
+ prec = buf_remaining - e - 1;
+ }
- 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 (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++;
+ }
+ }
}
- 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;
+ } 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 (!*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)++;
+ // 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++;
}
- for (i=10, e=9*(int)(r-a); *a>=i; i*=10, e++);
}
}
- 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 (fmt == 'e' && prec > (buf_remaining - 6)) {
+ prec = buf_remaining - 6;
}
- else {
- t-=2;
- p--;
+ // 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 (!(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));
+ if (fmt == 'f') {
+ dec = e;
+ num_digits = prec + e + 1;
+ } else {
+ e_sign = '+';
}
}
- 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;
+ if (prec < 0) {
+ // This can happen when the prec is trimmed to prevent buffer overflow
+ prec = 0;
}
- 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);
+ // 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;
}
- 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));
+ 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++ = '.';
}
- pad(f, '0', p+9, 9, 0);
+ f -= (mrb_float)d;
+ f *= 10.0;
}
- 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);
+
+ // 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--;
}
- 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 != '%') {
- return -1;
+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++;
}
- ++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 (ISDIGIT(*s)) {
+ char *endptr;
+ width = strtoul(s, &endptr, 10);
+ s = endptr;
}
-
- if (*fmt == '.') {
- ++fmt;
- for (p = 0; ISDIGIT(*fmt); ++fmt) {
- p = 10 * p + (*fmt - '0');
+ if (*s == '.') {
+ s++;
+ if (ISDIGIT(*s)) {
+ char *endptr;
+ prec = strtoul(s, &endptr, 10);
+ s = endptr;
+ }
+ else {
+ prec = 0;
}
}
- else {
- p = -1;
+ char c = *s;
+ if (alt_form) {
+ c |= 0x80;
}
-
- 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 len = mrb_format_float(f, buf, buf_size, c, prec, sign);
+
+ // 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--;
}
-}
-
-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");
+ if (*buf < '0' || *buf >= '9') {
+ // For inf or nan, we don't want to zero pad.
+ zero_pad = 0;
}
- *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);
+ 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;
}
-#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') {