Merge branch 'format-float' of github.com:dhylands/micropython into dhylands-format-float
This commit is contained in:
commit
0a8458c353
311
py/format-float.c
Normal file
311
py/format-float.c
Normal file
@ -0,0 +1,311 @@
|
||||
/***********************************************************************
|
||||
|
||||
format-float.c - Ruutine for converting a single-precision floating
|
||||
point number into a string.
|
||||
|
||||
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 original code can be found in https://github.com/dhylands/format-float
|
||||
|
||||
Dave Hylands
|
||||
|
||||
***********************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mpconfig.h"
|
||||
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
|
||||
#include "format-float.h"
|
||||
|
||||
// 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_SIGN_MASK 0x80000000
|
||||
#define FLT_EXP_MASK 0x7F800000
|
||||
#define FLT_MAN_MASK 0x007FFFFF
|
||||
|
||||
static const float g_pos_pow[] = {
|
||||
1e32, 1e16, 1e8, 1e4, 1e2, 1e1
|
||||
};
|
||||
static const float g_neg_pow[] = {
|
||||
1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1
|
||||
};
|
||||
|
||||
int format_float(float f, char *buf, size_t buf_size, char fmt, int prec, char sign) {
|
||||
|
||||
char *s = buf;
|
||||
int buf_remaining = buf_size - 1;
|
||||
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} num = {f};
|
||||
|
||||
if (buf_size < 7) {
|
||||
// Smallest exp notion is -9e+99 which is 6 chars plus terminating
|
||||
// nulll.
|
||||
|
||||
if (buf_size >= 2) {
|
||||
*s++ = '?';
|
||||
}
|
||||
if (buf_size >= 1) {
|
||||
*s++ = '\0';
|
||||
}
|
||||
return buf_size >= 2;
|
||||
}
|
||||
if (num.u & FLT_SIGN_MASK) {
|
||||
*s++ = '-';
|
||||
num.u &= ~FLT_SIGN_MASK;
|
||||
} else {
|
||||
if (sign) {
|
||||
*s++ = sign;
|
||||
}
|
||||
}
|
||||
buf_remaining -= (s - buf); // Adjust for sign
|
||||
|
||||
if ((num.u & FLT_EXP_MASK) == FLT_EXP_MASK) {
|
||||
char uc = fmt & 0x20;
|
||||
if ((num.u & FLT_MAN_MASK) == 0) {
|
||||
*s++ = 'I' ^ uc;
|
||||
*s++ = 'N' ^ uc;
|
||||
*s++ = 'F' ^ uc;
|
||||
} else {
|
||||
*s++ = 'N' ^ uc;
|
||||
*s++ = 'A' ^ uc;
|
||||
*s++ = 'N' ^ uc;
|
||||
}
|
||||
*s = '\0';
|
||||
return 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 float *pos_pow = g_pos_pow;
|
||||
const float *neg_pow = g_neg_pow;
|
||||
|
||||
if (num.u == 0) {
|
||||
e = 0;
|
||||
if (fmt == 'e') {
|
||||
e_sign = '+';
|
||||
} else if (fmt == 'f') {
|
||||
num_digits = prec + 1;
|
||||
}
|
||||
} else if (num.u < 0x3f800000) { // f < 1.0
|
||||
// Build negative exponent
|
||||
for (e = 0, e1 = 32; e1; e1 >>= 1, pos_pow++, neg_pow++) {
|
||||
if (*neg_pow > num.f) {
|
||||
e += e1;
|
||||
num.f *= *pos_pow;
|
||||
}
|
||||
}
|
||||
if (num.f < 1.0F && num.f >= 0.9999995F) {
|
||||
num.f = 1.0F;
|
||||
} else {
|
||||
e++;
|
||||
num.f *= 10.0F;
|
||||
}
|
||||
|
||||
// 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++ = '0';
|
||||
|
||||
if (prec + e + 1 > buf_remaining) {
|
||||
prec = buf_remaining - e - 1;
|
||||
}
|
||||
|
||||
if (org_fmt == 'g') {
|
||||
prec += (e - 1);
|
||||
}
|
||||
num_digits = prec;
|
||||
if (num_digits) {
|
||||
*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 = '-';
|
||||
dec = 0;
|
||||
|
||||
if (prec > (buf_remaining - 6)) {
|
||||
prec = buf_remaining - 6;
|
||||
if (fmt == 'g') {
|
||||
prec++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Build positive exponent
|
||||
for (e = 0, e1 = 32; e1; e1 >>= 1, pos_pow++, neg_pow++) {
|
||||
if (*pos_pow <= num.f) {
|
||||
e += e1;
|
||||
num.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 num.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) {
|
||||
int32_t d = num.f;
|
||||
*s++ = '0' + d;
|
||||
if (dec == 0 && prec > 0) {
|
||||
*s++ = '.';
|
||||
}
|
||||
num.f -= (float)d;
|
||||
num.f *= 10.0F;
|
||||
}
|
||||
|
||||
// Round
|
||||
if (num.f >= 5.0F) {
|
||||
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 (num.u < 0x3f800000 && fmt == 'f') {
|
||||
// We rounded up to 1.0
|
||||
prec--;
|
||||
}
|
||||
}
|
||||
|
||||
if (org_fmt == 'g' && prec > 0) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
#endif
|
3
py/format-float.h
Normal file
3
py/format-float.h
Normal file
@ -0,0 +1,3 @@
|
||||
int format_float(float f, char *buf, size_t bufSize, char fmt, int prec, char sign);
|
||||
|
||||
|
@ -12,12 +12,21 @@
|
||||
#include "runtime0.h"
|
||||
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
|
||||
#include "format-float.h"
|
||||
#endif
|
||||
|
||||
mp_obj_t mp_obj_new_float(mp_float_t value);
|
||||
|
||||
STATIC void float_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
|
||||
mp_obj_float_t *o = o_in;
|
||||
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
|
||||
char buf[32];
|
||||
format_float(o->value, buf, sizeof(buf), 'g', 6, '\0');
|
||||
print(env, "%s", buf);
|
||||
#else
|
||||
print(env, "%.8g", (double) o->value);
|
||||
#endif
|
||||
}
|
||||
|
||||
STATIC mp_obj_t float_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
|
||||
|
1
py/py.mk
1
py/py.mk
@ -32,6 +32,7 @@ PY_O_BASENAME = \
|
||||
asmthumb.o \
|
||||
emitnthumb.o \
|
||||
emitinlinethumb.o \
|
||||
format-float.o \
|
||||
parsenumbase.o \
|
||||
parsenum.o \
|
||||
runtime.o \
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define MICROPY_ENABLE_GC (1)
|
||||
#define MICROPY_ENABLE_REPL_HELPERS (1)
|
||||
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
|
||||
#define MICROPY_ENABLE_FLOAT (1)
|
||||
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
|
||||
#define MICROPY_PATH_MAX (128)
|
||||
/* Enable FatFS LFNs
|
||||
|
54
stm/printf.c
54
stm/printf.c
@ -11,6 +11,9 @@
|
||||
#include "lcd.h"
|
||||
#include "usart.h"
|
||||
#include "usb.h"
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
#include "format-float.h"
|
||||
#endif
|
||||
|
||||
#define PF_FLAG_LEFT_ADJUST (0x01)
|
||||
#define PF_FLAG_SHOW_SIGN (0x02)
|
||||
@ -210,29 +213,40 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
|
||||
chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, width);
|
||||
break;
|
||||
#if MICROPY_ENABLE_FLOAT
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'g':
|
||||
case 'G':
|
||||
{
|
||||
// This is a very hacky approach to printing floats. Micropython
|
||||
// uses %g when using print, and I just wanted to see somthing
|
||||
// usable. I expect that this will be replaced with something
|
||||
// more appropriate.
|
||||
char dot = '.';
|
||||
mp_float_t d = va_arg(args, double);
|
||||
int left = (int)d;
|
||||
int right = (int)((d - (mp_float_t)(int)d) * 1000000.0);
|
||||
if (right < 0) {
|
||||
if (left == 0) {
|
||||
chrs += pfenv_print_strn(pfenv, "-0", 2, flags, width);
|
||||
} else {
|
||||
chrs += pfenv_print_int(pfenv, left, 1, 10, 'a', flags, width);
|
||||
}
|
||||
chrs += pfenv_print_strn(pfenv, &dot, 1, flags, width);
|
||||
chrs += pfenv_print_int(pfenv, -right, 0, 10, 'a', PF_FLAG_ZERO_PAD, 6);
|
||||
} else {
|
||||
chrs += pfenv_print_int(pfenv, left, 1, 10, 'a', flags, width);
|
||||
chrs += pfenv_print_strn(pfenv, &dot, 1, flags, width);
|
||||
chrs += pfenv_print_int(pfenv, right, 0, 10, 'a', PF_FLAG_ZERO_PAD, 6);
|
||||
char buf[32];
|
||||
char sign = '\0';
|
||||
|
||||
if (flags & PF_FLAG_SHOW_SIGN) {
|
||||
sign = '+';
|
||||
}
|
||||
else
|
||||
if (flags & PF_FLAG_SPACE_SIGN) {
|
||||
sign = ' ';
|
||||
}
|
||||
float f = va_arg(args, double);
|
||||
int len = format_float(f, buf, sizeof(buf), *fmt, prec, sign);
|
||||
char *s = buf;
|
||||
|
||||
// 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' && (flags & PF_FLAG_ZERO_PAD)) {
|
||||
chrs += pfenv_print_strn(pfenv, &buf[0], 1, 0, 1);
|
||||
s++;
|
||||
width--;
|
||||
len--;
|
||||
}
|
||||
if (*s < '0' || *s >= '9') {
|
||||
// For inf or nan, we don't want to zero pad.
|
||||
flags &= ~PF_FLAG_ZERO_PAD;
|
||||
}
|
||||
chrs += pfenv_print_strn(pfenv, s, len, flags, width);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user