Skip to content

Commit

Permalink
parse the %e, %E, %a, %A format specifiers instead of just ignoring t…
Browse files Browse the repository at this point in the history
…hem. can't print them yet. (charlesnicholson#224)

* parse the %e, %E, %a, %A format specifiers instead of just ignoring them. can't print them yet.

* update readme
  • Loading branch information
charlesnicholson authored Sep 20, 2022
1 parent 1ea58a4 commit 90633b3
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 10 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ Like `printf`, `nanoprintf` expects a conversion specification string of the fol
* `x` / `X`: Unsigned hexadecimal integers
* `p`: Pointers
* `n`: Write the number of bytes written to the pointer vararg
* `f`/`F`: Floating-point values
* `f`/`F`: Floating-point decimal
* `e`/`E`: Floating-point scientific (unimplemented, prints decimal)
* `a`/`A`: Floating-point hex (unimplemented, prints decimal)
* `b`/`B`: Binary integers

## Floating Point
Expand All @@ -150,6 +152,8 @@ Floating point conversion is performed by extracting the value into 64:64 fixed-

Because the float -> fixed code operates on the raw float value bits, no floating point operations are performed. This allows nanoprintf to efficiently format floats on soft-float architectures like Cortex-M0, and to function identically with or without optimizations like "fast math". Despite `nano` in the name, there's no way to do away with double entirely, since the C language standard says that floats are promoted to double any time they're passed into variadic argument lists. nanoprintf casts all doubles back down to floats before doing any conversions. No other single- or double- precision operations are performed.

The `%e`/`%E` and `%a`/`%A` specifiers are parsed but not formatted. If used, the output will be identical to if `%f`/`%F` was used. Pull requests welcome! :)

## Limitations

No wide-character support exists: the `%lc` and `%ls` fields require that the arg be converted to a char array as if by a call to [wcrtomb](http://man7.org/linux/man-pages/man3/wcrtomb.3.html). When locale and character set conversions get involved, it's hard to keep the name "nano". Accordingly, `%lc` and `%ls` behave like `%c` and `%s`, respectively.
Expand Down
32 changes: 25 additions & 7 deletions nanoprintf.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,9 @@ typedef enum {
, NPF_FMT_SPEC_CONV_WRITEBACK // 'n'
#endif
#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
, NPF_FMT_SPEC_CONV_FLOAT_DECIMAL // 'f', 'F'
, NPF_FMT_SPEC_CONV_FLOAT_DEC // 'f', 'F'
, NPF_FMT_SPEC_CONV_FLOAT_SCI // 'e', 'E'
, NPF_FMT_SPEC_CONV_FLOAT_HEX // 'a', 'A'
#endif
} npf_format_spec_conversion_t;

Expand Down Expand Up @@ -435,7 +437,21 @@ int npf_parse_format_spec(char const *format, npf_format_spec_t *out_spec) {
case 'F':
out_spec->case_adjust = 0;
case 'f':
out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_DECIMAL;
out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_DEC;
if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
break;

case 'E':
out_spec->case_adjust = 0;
case 'e':
out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_SCI;
if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
break;

case 'A':
out_spec->case_adjust = 0;
case 'a':
out_spec->conv_spec = NPF_FMT_SPEC_CONV_FLOAT_HEX;
if (out_spec->prec_opt == NPF_FMT_SPEC_OPT_NONE) { out_spec->prec = 6; }
break;
#endif
Expand Down Expand Up @@ -861,7 +877,9 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) {
#endif

#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
case NPF_FMT_SPEC_CONV_FLOAT_DECIMAL: {
case NPF_FMT_SPEC_CONV_FLOAT_DEC:
case NPF_FMT_SPEC_CONV_FLOAT_SCI:
case NPF_FMT_SPEC_CONV_FLOAT_HEX: {
float val;
if (fs.length_modifier == NPF_FMT_SPEC_LEN_MOD_LONG_DOUBLE) {
val = (float)va_arg(args, long double);
Expand Down Expand Up @@ -911,7 +929,7 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) {
#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
if (!inf_or_nan) { // float precision is after the decimal point
int const prec_start =
(fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DECIMAL) ? frac_chars : cbuf_len;
(fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC) ? frac_chars : cbuf_len;
prec_pad = npf_max(0, fs.prec - prec_start);
}
#elif NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
Expand All @@ -925,7 +943,7 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) {
if (need_0x) { field_pad -= 2; }

#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DECIMAL) && !fs.prec && !fs.alt_form) {
if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC) && !fs.prec && !fs.alt_form) {
++field_pad; // 0-pad, no decimal point.
}
#endif
Expand Down Expand Up @@ -956,7 +974,7 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) {
} else {
if (sign_c) { NPF_PUTC(sign_c); }
#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
if (fs.conv_spec != NPF_FMT_SPEC_CONV_FLOAT_DECIMAL) {
if (fs.conv_spec != NPF_FMT_SPEC_CONV_FLOAT_DEC) {
#endif

#if NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS == 1
Expand All @@ -980,7 +998,7 @@ int npf_vpprintf(npf_putc pc, void *pc_ctx, char const *format, va_list args) {

#if NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS == 1
// real precision comes after the number.
if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DECIMAL) && !inf_or_nan) {
if ((fs.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC) && !inf_or_nan) {
while (prec_pad-- > 0) { NPF_PUTC('0'); }
}
#endif
Expand Down
28 changes: 26 additions & 2 deletions tests/unit_parse_format_spec.cc
Original file line number Diff line number Diff line change
Expand Up @@ -430,13 +430,37 @@ TEST_CASE("npf_parse_format_spec") {

SUBCASE("f") {
REQUIRE(npf_parse_format_spec("%f", &spec) == 2);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DECIMAL);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC);
REQUIRE(spec.case_adjust == 'a' - 'A');
}

SUBCASE("F") {
REQUIRE(npf_parse_format_spec("%F", &spec) == 2);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DECIMAL);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_DEC);
REQUIRE(spec.case_adjust == 0);
}

SUBCASE("e") {
REQUIRE(npf_parse_format_spec("%e", &spec) == 2);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_SCI);
REQUIRE(spec.case_adjust == 'a' - 'A');
}

SUBCASE("E") {
REQUIRE(npf_parse_format_spec("%E", &spec) == 2);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_SCI);
REQUIRE(spec.case_adjust == 0);
}

SUBCASE("a") {
REQUIRE(npf_parse_format_spec("%a", &spec) == 2);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX);
REQUIRE(spec.case_adjust == 'a' - 'A');
}

SUBCASE("A") {
REQUIRE(npf_parse_format_spec("%A", &spec) == 2);
REQUIRE(spec.conv_spec == NPF_FMT_SPEC_CONV_FLOAT_HEX);
REQUIRE(spec.case_adjust == 0);
}

Expand Down

0 comments on commit 90633b3

Please sign in to comment.