Skip to content

Commit ca5a241

Browse files
committed
Add proper floating point printing support.
1 parent 0308f96 commit ca5a241

6 files changed

Lines changed: 359 additions & 20 deletions

File tree

py/format-float.c

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
/***********************************************************************
2+
3+
format-float.c - Ruutine for converting a single-precision floating
4+
point number into a string.
5+
6+
The code in this funcion was inspired from Fred Bayer's pdouble.c.
7+
Since pdouble.c was released as Public Domain, I'm releasing this
8+
code as public domain as well.
9+
10+
The original code can be found in https://github.com/dhylands/format-float
11+
12+
Dave Hylands
13+
14+
***********************************************************************/
15+
16+
#include <stdint.h>
17+
#include <stdlib.h>
18+
19+
#include "mpconfig.h"
20+
21+
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
22+
#include "format-float.h"
23+
24+
// 1 sign bit, 8 exponent bits, and 23 mantissa bits.
25+
// exponent values 0 and 255 are reserved, exponent can be 1 to 254.
26+
// exponent is stored with a bias of 127.
27+
// The min and max floats are on the order of 1x10^37 and 1x10^-37
28+
29+
#define FLT_SIGN_MASK 0x80000000
30+
#define FLT_EXP_MASK 0x7F800000
31+
#define FLT_MAN_MASK 0x007FFFFF
32+
33+
static const float g_pos_pow[] = {
34+
1e32, 1e16, 1e8, 1e4, 1e2, 1e1
35+
};
36+
static const float g_neg_pow[] = {
37+
1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1
38+
};
39+
40+
int format_float(float f, char *buf, size_t buf_size, char fmt, int prec, char sign) {
41+
42+
char *s = buf;
43+
int buf_remaining = buf_size - 1;
44+
45+
union {
46+
float f;
47+
uint32_t u;
48+
} num = {f};
49+
50+
if (buf_size < 7) {
51+
// Smallest exp notion is -9e+99 which is 6 chars plus terminating
52+
// nulll.
53+
54+
if (buf_size >= 2) {
55+
*s++ = '?';
56+
}
57+
if (buf_size >= 1) {
58+
*s++ = '\0';
59+
}
60+
return buf_size >= 2;
61+
}
62+
if (num.u & FLT_SIGN_MASK) {
63+
*s++ = '-';
64+
num.u &= ~FLT_SIGN_MASK;
65+
} else {
66+
if (sign) {
67+
*s++ = sign;
68+
}
69+
}
70+
buf_remaining -= (s - buf); // Adjust for sign
71+
72+
if ((num.u & FLT_EXP_MASK) == FLT_EXP_MASK) {
73+
char uc = fmt & 0x20;
74+
if ((num.u & FLT_MAN_MASK) == 0) {
75+
*s++ = 'I' ^ uc;
76+
*s++ = 'N' ^ uc;
77+
*s++ = 'F' ^ uc;
78+
} else {
79+
*s++ = 'N' ^ uc;
80+
*s++ = 'A' ^ uc;
81+
*s++ = 'N' ^ uc;
82+
}
83+
*s = '\0';
84+
return s - buf;
85+
}
86+
87+
if (prec < 0) {
88+
prec = 6;
89+
}
90+
char e_char = 'E' | (fmt & 0x20); // e_char will match case of fmt
91+
fmt |= 0x20; // Force fmt to be lowercase
92+
char org_fmt = fmt;
93+
if (fmt == 'g' && prec == 0) {
94+
prec = 1;
95+
}
96+
int e, e1;
97+
int dec = 0;
98+
char e_sign = '\0';
99+
int num_digits = 0;
100+
const float *pos_pow = g_pos_pow;
101+
const float *neg_pow = g_neg_pow;
102+
103+
if (num.u == 0) {
104+
e = 0;
105+
if (fmt == 'e') {
106+
e_sign = '+';
107+
} else if (fmt == 'f') {
108+
num_digits = prec + 1;
109+
}
110+
} else if (num.u < 0x3f800000) { // f < 1.0
111+
// Build negative exponent
112+
for (e = 0, e1 = 32; e1; e1 >>= 1, pos_pow++, neg_pow++) {
113+
if (*neg_pow > num.f) {
114+
e += e1;
115+
num.f *= *pos_pow;
116+
}
117+
}
118+
if (num.f < 1.0F && num.f >= 0.9999995F) {
119+
num.f = 1.0F;
120+
} else {
121+
e++;
122+
num.f *= 10.0F;
123+
}
124+
125+
// If the user specified 'g' format, and e is <= 4, then we'll switch
126+
// to the fixed format ('f')
127+
128+
if (fmt == 'f' || (fmt == 'g' && e <= 4)) {
129+
fmt = 'f';
130+
dec = -1;
131+
*s++ = '0';
132+
133+
if (prec + e + 1 > buf_remaining) {
134+
prec = buf_remaining - e - 1;
135+
}
136+
137+
if (org_fmt == 'g') {
138+
prec += (e - 1);
139+
}
140+
num_digits = prec;
141+
if (num_digits) {
142+
*s++ = '.';
143+
while (--e && num_digits) {
144+
*s++ = '0';
145+
num_digits--;
146+
}
147+
}
148+
} else {
149+
// For e & g formats, we'll be printing the exponent, so set the
150+
// sign.
151+
e_sign = '-';
152+
dec = 0;
153+
154+
if (prec > (buf_remaining - 6)) {
155+
prec = buf_remaining - 6;
156+
if (fmt == 'g') {
157+
prec++;
158+
}
159+
}
160+
}
161+
} else {
162+
// Build positive exponent
163+
for (e = 0, e1 = 32; e1; e1 >>= 1, pos_pow++, neg_pow++) {
164+
if (*pos_pow <= num.f) {
165+
e += e1;
166+
num.f *= *neg_pow;
167+
}
168+
}
169+
170+
// If the user specified fixed format (fmt == 'f') and e makes the
171+
// number too big to fit into the available buffer, then we'll
172+
// switch to the 'e' format.
173+
174+
if (fmt == 'f') {
175+
if (e >= buf_remaining) {
176+
fmt = 'e';
177+
} else if ((e + prec + 2) > buf_remaining) {
178+
prec = buf_remaining - e - 2;
179+
if (prec < 0) {
180+
// This means no decimal point, so we can add one back
181+
// for the decimal.
182+
prec++;
183+
}
184+
}
185+
}
186+
if (fmt == 'e' && prec > (buf_remaining - 6)) {
187+
prec = buf_remaining - 6;
188+
}
189+
// If the user specified 'g' format, and e is < prec, then we'll switch
190+
// to the fixed format.
191+
192+
if (fmt == 'g' && e < prec) {
193+
fmt = 'f';
194+
prec -= (e + 1);
195+
}
196+
if (fmt == 'f') {
197+
dec = e;
198+
num_digits = prec + e + 1;
199+
} else {
200+
e_sign = '+';
201+
}
202+
}
203+
if (prec < 0) {
204+
// This can happen when the prec is trimmed to prevent buffer overflow
205+
prec = 0;
206+
}
207+
208+
// We now have num.f as a floating point number between >= 1 and < 10
209+
// (or equal to zero), and e contains the absolute value of the power of
210+
// 10 exponent. and (dec + 1) == the number of dgits before the decimal.
211+
212+
// For e, prec is # digits after the decimal
213+
// For f, prec is # digits after the decimal
214+
// For g, prec is the max number of significant digits
215+
//
216+
// For e & g there will be a single digit before the decimal
217+
// for f there will be e digits before the decimal
218+
219+
if (fmt == 'e') {
220+
num_digits = prec + 1;
221+
} else if (fmt == 'g') {
222+
if (prec == 0) {
223+
prec = 1;
224+
}
225+
num_digits = prec;
226+
}
227+
228+
// Print the digits of the mantissa
229+
for (int i = 0; i < num_digits; ++i, --dec) {
230+
int32_t d = num.f;
231+
*s++ = '0' + d;
232+
if (dec == 0 && prec > 0) {
233+
*s++ = '.';
234+
}
235+
num.f -= (float)d;
236+
num.f *= 10.0F;
237+
}
238+
239+
// Round
240+
if (num.f >= 5.0F) {
241+
char *rs = s;
242+
rs--;
243+
while (1) {
244+
if (*rs == '.') {
245+
rs--;
246+
continue;
247+
}
248+
if (*rs < '0' || *rs > '9') {
249+
// + or -
250+
rs++; // So we sit on the digit to the right of the sign
251+
break;
252+
}
253+
if (*rs < '9') {
254+
(*rs)++;
255+
break;
256+
}
257+
*rs = '0';
258+
if (rs == buf) {
259+
break;
260+
}
261+
rs--;
262+
}
263+
if (*rs == '0') {
264+
// We need to insert a 1
265+
if (rs[1] == '.' && fmt != 'f') {
266+
// We're going to round 9.99 to 10.00
267+
// Move the decimal point
268+
rs[0] = '.';
269+
rs[1] = '0';
270+
if (e_sign == '-') {
271+
e--;
272+
} else {
273+
e++;
274+
}
275+
}
276+
s++;
277+
char *ss = s;
278+
while (ss > rs) {
279+
*ss = ss[-1];
280+
ss--;
281+
}
282+
*rs = '1';
283+
}
284+
if (num.u < 0x3f800000 && fmt == 'f') {
285+
// We rounded up to 1.0
286+
prec--;
287+
}
288+
}
289+
290+
if (org_fmt == 'g' && prec > 0) {
291+
// Remove trailing zeros and a trailing decimal point
292+
while (s[-1] == '0') {
293+
s--;
294+
}
295+
if (s[-1] == '.') {
296+
s--;
297+
}
298+
}
299+
// Append the exponent
300+
if (e_sign) {
301+
*s++ = e_char;
302+
*s++ = e_sign;
303+
*s++ = '0' + (e / 10);
304+
*s++ = '0' + (e % 10);
305+
}
306+
*s = '\0';
307+
308+
return s - buf;
309+
}
310+
311+
#endif

py/format-float.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int format_float(float f, char *buf, size_t bufSize, char fmt, int prec, char sign);
2+
3+

py/objfloat.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,21 @@
1212
#include "runtime0.h"
1313

1414
#if MICROPY_ENABLE_FLOAT
15+
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
16+
#include "format-float.h"
17+
#endif
1518

1619
mp_obj_t mp_obj_new_float(mp_float_t value);
1720

1821
STATIC void float_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
1922
mp_obj_float_t *o = o_in;
23+
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
24+
char buf[32];
25+
format_float(o->value, buf, sizeof(buf), 'g', 6, '\0');
26+
print(env, "%s", buf);
27+
#else
2028
print(env, "%.8g", (double) o->value);
29+
#endif
2130
}
2231

2332
STATIC mp_obj_t float_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {

py/py.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ PY_O_BASENAME = \
3232
asmthumb.o \
3333
emitnthumb.o \
3434
emitinlinethumb.o \
35+
format-float.o \
3536
parsenumbase.o \
3637
parsenum.o \
3738
runtime.o \

stm/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define MICROPY_ENABLE_GC (1)
88
#define MICROPY_ENABLE_REPL_HELPERS (1)
99
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
10+
#define MICROPY_ENABLE_FLOAT (1)
1011
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
1112
#define MICROPY_PATH_MAX (128)
1213
/* Enable FatFS LFNs

0 commit comments

Comments
 (0)