mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
364 lines
8.4 KiB
C
364 lines
8.4 KiB
C
#include <mruby.h>
|
|
#include <string.h>
|
|
|
|
#ifndef MRB_NO_FLOAT
|
|
/***********************************************************************
|
|
|
|
Routine for converting a single-precision
|
|
floating point number into a string.
|
|
|
|
The code in this function 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.
|
|
|
|
Dave Hylands
|
|
|
|
The original code can be found in https://github.com/dhylands/format-float
|
|
***********************************************************************/
|
|
|
|
/***********************************************************************
|
|
|
|
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 <math.h>
|
|
|
|
#ifdef MRB_USE_FLOAT32
|
|
|
|
// 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
|
|
|
|
#define FLT_DECEXP 32
|
|
#define FLT_ROUND_TO_ONE 0.9999995F
|
|
#define FLT_MIN_BUF_SIZE 6 // -9e+99
|
|
|
|
#else
|
|
|
|
// 1 sign bit, 11 exponent bits, and 52 mantissa bits.
|
|
|
|
#define FLT_DECEXP 256
|
|
#define FLT_ROUND_TO_ONE 0.999999999995
|
|
#define FLT_MIN_BUF_SIZE 7 // -9e+199
|
|
|
|
#endif /* MRB_USE_FLOAT32 */
|
|
|
|
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
|
|
};
|
|
|
|
/*
|
|
* 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 numbers.
|
|
*/
|
|
|
|
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; /* 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 -= (int)(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 (int)(s - buf);
|
|
}
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
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 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 (org_fmt == 'g') {
|
|
prec += (e - 1);
|
|
}
|
|
// truncate precision to prevent buffer overflow
|
|
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 {
|
|
// 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 - FLT_MIN_BUF_SIZE)) {
|
|
prec = buf_remaining - FLT_MIN_BUF_SIZE;
|
|
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 = (int8_t)((int)f)%10;
|
|
*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;
|
|
if (e >= 100) {
|
|
*s++ = '0' + (e / 100);
|
|
e %= 100;
|
|
}
|
|
*s++ = '0' + (e / 10);
|
|
*s++ = '0' + (e % 10);
|
|
}
|
|
*s = '\0';
|
|
|
|
return (int)(s - buf);
|
|
}
|
|
#endif
|