#include <mruby.h>
#include <string.h>
#ifndef MRB_NO_FLOAT
#include <math.h>
#ifdef MRB_USE_FLOAT32
#define FLT_DECEXP 32
#define FLT_ROUND_TO_ONE 0.9999995F
#define FLT_MIN_BUF_SIZE 6
#else
#define FLT_DECEXP 256
#define FLT_ROUND_TO_ONE 0.999999999995
#define FLT_MIN_BUF_SIZE 7
#endif
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
};
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 = (int)buf_size - 1;
int alt_form = 0;
if ((uint8_t)fmt & 0x80) {
fmt &= 0x7f;
alt_form = 1;
}
if (buf_size <= FLT_MIN_BUF_SIZE) {
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 -= (int)(s - buf);
{
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 (int)(s - buf);
}
}
if (prec < 0) {
prec = 6;
}
char e_char = 'E' | (fmt & 0x20); fmt |= 0x20; 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) { char first_dig = '0';
if (f >= FLT_ROUND_TO_ONE) {
first_dig = '1';
}
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*neg_pow > f) {
e += e1;
f *= *pos_pow;
}
}
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;
}
}
if (fmt == 'f' || (fmt == 'g' && e <= 4)) {
fmt = 'f';
dec = -1;
*s++ = first_dig;
if (org_fmt == 'g') {
prec += (e - 1);
}
if (prec + 2 > buf_remaining) {
prec = buf_remaining - 2;
}
num_digits = prec;
if (num_digits || alt_form) {
*s++ = '.';
while (--e && num_digits) {
*s++ = '0';
num_digits--;
}
}
} else {
e_sign = e_sign_char;
dec = 0;
if (prec > (buf_remaining - FLT_MIN_BUF_SIZE)) {
prec = buf_remaining - FLT_MIN_BUF_SIZE;
if (fmt == 'g') {
prec++;
}
}
}
} else {
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*pos_pow <= f) {
e += e1;
f *= *neg_pow;
}
}
if (fmt == 'f') {
if (e >= buf_remaining) {
fmt = 'e';
} else if ((e + prec + 2) > buf_remaining) {
prec = buf_remaining - e - 2;
if (prec < 0) {
prec++;
}
}
}
if (fmt == 'e' && prec > (buf_remaining - 6)) {
prec = buf_remaining - 6;
}
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) {
prec = 0;
}
if (fmt == 'e') {
num_digits = prec + 1;
} else if (fmt == 'g') {
if (prec == 0) {
prec = 1;
}
num_digits = prec;
}
for (int i = 0; i < num_digits; i++,dec--) {
int8_t d = (int8_t)((int)f)%10;
*s++ = '0' + d;
if (dec == 0 && (prec > 0 || alt_form)) {
*s++ = '.';
}
f -= (mrb_float)d;
f *= 10.0;
}
if (f >= 5.0) {
char *rs = s;
rs--;
while (1) {
if (*rs == '.') {
rs--;
continue;
}
if (*rs < '0' || *rs > '9') {
rs++; break;
}
if (*rs < '9') {
(*rs)++;
break;
}
*rs = '0';
if (rs == buf) {
break;
}
rs--;
}
if (*rs == '0') {
if (rs[1] == '.' && fmt != 'f') {
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') {
prec--;
}
}
}
if (org_fmt == 'g' && prec > 0 && !alt_form) {
while (s[-1] == '0') {
s--;
}
if (s[-1] == '.') {
s--;
}
}
if (e_sign) {
*s++ = e_char;
*s++ = e_sign;
if (e >= 100) {
*s++ = '0' + (e / 100);
e %= 100;
}
*s++ = '0' + (e / 10);
*s++ = '0' + (e % 10);
}
*s = '\0';
return (int)(s - buf);
}
#endif