mpaland / printf Goto Github PK
View Code? Open in Web Editor NEWTiny, fast, non-dependent and fully loaded printf implementation for embedded systems. Extensive test suite passing.
License: MIT License
Tiny, fast, non-dependent and fully loaded printf implementation for embedded systems. Extensive test suite passing.
License: MIT License
Is there any special reason why you use cpp?
many thanks
Attaching a test to reproduce and a proposed patch to fix.
Try running the test with linux system printf and the result is:
Print with precison -2:
| 9| |9 | | 9|
| 10| |10| | 10|
Print with precision -12:
| 9| |9 | | 9|
| 10| |10 | | 10|
With your printf:
Print with precison -2:
| 9| |9| | 9|
| 10| |10| | 10|
Print with precision -12:
| 9| |9 | | 9|
| 10| |10 | | 10|
Note the missing padding.
See the attached proposed fix.
Hi!!
Using your kawaii pwintf impwementation,
the compiler gets all funny about some silly size_t.
printf.h:78:29: error: unknown type name 'size_t'
int snprintf(char* buffer, size_t count, const char* format, ...);
^~~~~~
It even dwew a line for us owo
I did some wesearch and it said, there's no sizy-wizy_t in stdarg.h.
Instead, I moved stddef.h fwom pwintf.c to pwintf.h, because
stddef.h is like totally the hood of size_t.
This fixie wixies the pwoblem
xD *starts twerking*
Hi, it seems that when the width specifier matches the number of digits in a negative number, one of the digits gets removed from the output, i.e.
printf_ ("%2d", -123); // --> prints -123 (ok)
printf_ ("%3d", -123); // --> printf -23 ('1' is missing)
printf_ ("%4d", -123); // --> printf -123 (ok)
The same issue happens with ("%1d", -1)
or ("%2d", -12)
, for example.
The point where this happens is this line:
// when len == width, removes the last digit in the inverted output,
// before writing the `-` character
if (len && (len == width) && (negative || (flags & FLAGS_PLUS) || (flags & FLAGS_SPACE))) {
len--;
}
Hi,
On small targets I do not use Float, Long and I also do not need a big buffer size. Can you made the code more configurable. I want to use the source as submodule as it is without any changes if possible. So it would be gread if you can add this or something else which solves the problem
// buffer size used for printf (created on stack)
#ifndef PRINTF_BUFFER_SIZE
#define PRINTF_BUFFER_SIZE 128U
#endif
// ntoa conversion buffer size, this must be big enough to hold one converted numeric number (created on stack)
#ifndef NTOA_BUFFER_SIZE
#define NTOA_BUFFER_SIZE 32U
#endif
// ftoa conversion buffer size, this must be big enough to hold one converted float number (created on stack)
#ifndef FTOA_BUFFER_SIZE
#define FTOA_BUFFER_SIZE 32U
#endif
// define this to support floating point (%f)
#ifndef PRINTF_NO_FLOAT_SUPPORT
#define PRINTF_FLOAT_SUPPORT
#endif
// define this to support long long types (%llu or %p)
#ifndef PRINTF_NO_LONG_LONG_SUPPORT
#define PRINTF_LONG_LONG_SUPPORT
#endif
so no I can use
#include "printf_config.h"
#include "printf.h"
In the printf config i use
#define PRINTF_BUFFER_SIZE 32
#define PRINTF_NO_FLOAT_SUPPORT
#define PRINTF_NO_LONG_LONG_SUPPORT
many thanks
cheers
mathias
There are some possible optimizations (at least for ARM CPU):
Only 4 arguments are passed in registers, remaining arguments has to be pushed on stack and stack frame may be created
out
,buffer
, maxlen
are passed around a lot. This can be replaced by passing
struct out_object {
void (*fn)(out_object * out, char character, size_t idx);
}
struct out_object_buffer {
struct out_object_base base;
char* buffer;
}
static void out_buffer(out_object * out, char character, size_t idx)
struct out_object_buffer *self = (struct out_object_buffer*)out;
self->buffer[idx] = character;
}
struct out_object_buffer_n {
struct out_object_base base;
char* buffer;
size_t maxlen;
}
static void out_buffer_n(out_object * out, char character, size_t idx)
struct out_object_buffer_n *self = (struct out_object_buffer_n*)out;
if(idx < self->maxlen)
self->buffer[idx] = character;
}
In code, call out->fn(out, c, idx);
. fn
is only single memory fetch and first argument is single register move)
idx
can be hidden in buffer object too (increment buffer
in object, keep maxlen as pointer to end of buffer). This will avoid all idx
handling in printf code, which must make no difference (idx
must be incremented after each out call or _out_fct
won't work)
BTW: inline
makes no sense for _out_
* functions, address for function is taken, so no inlining is possible unless _vsnprintf
is inlined too
flags
, base
, prec
, width
, possibly negative
(in flags?)may be packed into structure and passed by pointer. This will only need some care when this structure is modified.
There is subtle bug in _out_char
- printf("X%cX", '\0');
will emit only 2 characters instead of three. Special-casing termination (possibly even as extra function pointer in out_object
will handle this, it will improve code efficiency a bit (1 skipped test for each character) and as a bonus it will make it possible to implement buffering output function (flush on chunk size or end of format)
Another nice optimization would be to replace character output with write()
-like function. This will considerably limit number of through-pointer calls and save some instructions (test buffer size only once per out call, then memcpy [possibly optimizing copy to use word access])
All that is necessary is to emit *format
only when %
is encountered and implement in-place reversing in out_rev
and possibly use const char pad0[8]="00000000"; const char padSpc[8]=" ";
for optimized padding.
I can (and probably will) make most of the changes, are you interested in merging them?
Couple of minor issues I've spotted:
_vsnprintf
, can easily be fixedNULL
buffer and size 0
)Here are a couple test-cases to reproduce:
int size = test::snprintf(buf, 10, "hello, world");
REQUIRE(size == 12); // fails, 9 is returned instead
int size = test::snprintf(buf, 3, "%d", 10000);
REQUIRE(size == 2); // fails, 5 is returned and the buffer is overflowed
REQUIRE(strlen(buf) == 2);
REQUIRE(buf[0] == '1');
REQUIRE(buf[1] == '2');
REQUIRE(buf[2] == '\0');
The C99 spec defines the following additional modifiers:
hh A following integer conversion corresponds to a signed char or
unsigned char argument, or a following n conversion corresponds
to a pointer to a signed char argument.
h A following integer conversion corresponds to a short int or unsigned
short int argument, or a following n conversion corresponds to a
pointer to a short int argument.
L A following a, A, e, E, f, F, g, or G conversion corresponds to a
long double argument.
j A following integer conversion corresponds to an intmax_t or
uintmax_t argument, or a following n conversion corresponds to a
pointer to an intmax_t argument.
t A following integer conversion corresponds to a ptrdiff_t argument,
or a following n conversion corresponds to a pointer to a ptrdiff_t
argument.
It would be good to support those for people that want a standard-compliant printf replacement.
h
and hh
should be relatively straightforward, as char
and short
decay to int
in varargs; so they should just be accepted, but do nothing.
Hi, just a tiny remark: the comment for snprintf
/vsnprintf
seems to be incorrect:
(returns) The number of characters that are WRITTEN into the buffer, not counting the terminating null character. If the formatted string is truncated the buffer size (count) is returned
The description in the readme file is ok:
Anyway, if the output was truncated due to this limit, the return value is the number of characters that could have been written. Notice that a value equal or larger than
count
indicates a truncation.
I made a quick update so I'll place a PR (I didn't find the -1
being returned from anywhere in case of errors, so I didn't mention it explicitly).
Round-to-even in %f
is not working correctly, maybe it would be best to simply discard it completely? Opinions?
unsigned long whole = (unsigned long)value;
double tmp = (value - whole) * pow10[prec];
unsigned long frac = (unsigned long)tmp;
diff = tmp - frac;
The problem is that tmp
value is multiplied by pow10
.
With %.1f
, 0.95:
0.95 is represented as 0.94999999999999996 in binary. This should be rounded down.
But (0.95 - 0) * 10
is 9.5, exactly representable in binary and tie-decision code takes place, rounding to even and outputting "1.0"
.
It may be possible to fix this double-rounding (at least in some cases), but IMO it's not worth the complexity. It may be simpler to just remove the tie-handling code and live with it
Just curious about the motivation behind the name change (to have functions appended with an underscore, and then redefined without the underscore).
#define printf printf_
int printf_(const char* format, ...);
This caused some problems with my library when I updated. I have a libc which I use for embedded systems, but on host machines I link against the standard printf. With the new naming convention, I can no longer keep the printf.h header included and link against the std library - printf_
is undefined, since the host library defines printf
.
In using the fctprintf
printf function, I notice that the NULL terminator is passed to the out
function. In my experience, printing into a buffer should result in a NULL-terminator, but printf
itself does not print the NULL
byte.
My personal opinion is that fctprintf
should match the printf
behavior (updated in #19). Curious about your thoughts on the matter.
Compiling with GCC using -Wfloat-equal turned on, I get the following warnings:
printf.c:346:18: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
printf.c:363:20: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
The code does has a compare to 0.5
Any ideas on how to resolve this?
Thank you for creating this wonderful function!
I propose to add prefix to all exposed functions using eg. macro. This is to avoid possible name collision with stdio and other libraries when used.
What do you think?
When printing "%e" or "%g" with zero as argument, 0e-308 is assumed instead of zero
printf("%e", 0):
expected: "0.000000e+00"
got: "0.000000e-308"
printf("%g", 0):
expected: "0"
got: "0.000000e-308"
Hi,
It would be really nice if printf("%#b", 6) == "0b110"
!
Thanks !
In printf.h
you would have something like this:
void x_putchar(char character);
int x_printf(const char* format, ...);
int x_sprintf(char* buffer, const char* format, ...);
int x_snprintf(char* buffer, size_t count, const char* format, ...);
int x_vsnprintf(char* buffer, size_t count, const char* format, va_list va);
int x_fctprintf(void (*out)(char character), const char* format, ...);
Along with a set of macros:
#define vsnprintf x_vsnprintf
#define snprintf x_snprintf
...
This is what tinyprintf does and it is very convenient because it avoids conflicts with the STD printf functions.
I'm actually coming from tinyprintf because it lacks float support and doesn't seem to be updated often.
printf("%g", 0.0001) :
expected: "0.0001"
got: "0.000100000"
printf("%g", 0.00001) :
expected: "1e-05"
got : "1.000000e-05"
Hatchling Platform is a small C++ run-time intended to be developed against on the desktop before cross compiling to an embedded target.
It is using your printf and I would like to be mentioned in your "Using printf" section.
The project page is at: https://github.com/adrian3git/HatchlingPlatform
Thank you, and let me know if you have any questions.
In this case (and wider precision) octal zero should be included in padding instead of adding it
printf("%#.5o", 06143)
expected: 06143
got: 006143
printf("%#.6o", 06143)
expected: 006143
got: 0006143
another partially-related case is
sprintf("%#.0o", 0)
glibc: "0"
got: ""
The return value should return the number of bytes written, without the terminating zero (as mentioned by the printf spec and your source code comments).
Although, the following returns 10, instead of 9:
char buf[10];
int size = snprintf(buf, 10, "hello, world");
/* size is 10, but should be 9. */
Test case: printf("a%-5.1f", 0.5);
should print a0.5
.
The float code uses the absolute position to compute the amount of padding:
https://github.com/mpaland/printf/blob/master/printf.c#L391
It should use the index within the text produced by the format specifier, like the integer padding code does.
Hello,
Thank you for the fantastic work.
Should printf append a null terminator at the end of the formatted output? The standard seems to be unclear about this. The only statement I could find in the c11 standard regarding this is "The fprintf function returns when the end of the format string is encountered." [§7.21.6.1.2] (according to the standard, printf is equivalent to fprintf except for the first argument).
printf("%#08x %#0.8x\n",0x614e,0x614e);
// currently:
0x00614e 0x00614e
// standard;
0x00614e 0x0000614e
(edited to show same bad behaviour, but with precision <= 9 instead of 10)
Hello,
I think you could add a vprintf
implementation as below. I've done this for my libc
, but feel free to incorporate it on your end. (vprintf
is required by libc++)
int vprintf(const char *restrict format, va_list va)
{
char buffer[1];
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
}
sprintf("%#4x", 0x1234);
produces `"0x34"
It is caused by wrong '0x' space compensation when width equals output
BTW: I'm fixing issues reported, no need to work on it immediately
Senpai,
your little kawaii pwintf impwementation is getting out of contwol.
Pwease have a look at this:
The pwintf function:
int printf(const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_char, NULL, (size_t)-1, format, va);
va_end(va);
return ret;
}
The _vsnpwintf function:
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
{
unsigned int flags, width, precision, n;
size_t idx = 0U;
if (!buffer) {
// use null output function
out = _out_null;
}
...
Since we pass null fow the buffer, the output function is being set to _out_null,
which doesn't do anything. Pwintf has ceased functioning entirely.
Oh no!
I've added a wrapper around your library for projects using the Arduino SDK:
https://github.com/embeddedartistry/arduino-printf
Still my favorite printf library!
I use this function in freertos. It makes freertos stucked in one task(thread, can't switch to other tasks). And when I call osDelay, it returns osErrorISR.
There are some problems with a precision of 0 (an annoying corner case).
The following crash:
printf("% .0d", 0);
(should output
)printf("%+.0d", 0);
(should output +
)This is caused by decrementing a 0 len
here: https://github.com/mpaland/printf/blob/master/printf.c#L185
printf("%#.0x", 0);
is apparently supposed to output an empty string, not 0x
. The octal case is correct, though. It seems this was clarified in C11.
Hi,
Today I made at first test on my stm32 board with your lib. First impression works great an compiles without any warning on gnu_arm_gcc 5.x
One usage question... normaly I use :
printf("\nHallo Welt");
if I would like to print something at the beginning of a new line. But with your implementation I do also need some additional line feed - otherwise the line does not start at position 0 on a terminal:
printf("\n\rHallo Welt");
So I am wondering if the _printchar should handles this is the printf implementation responsible for that?
many thanks
Hi,
It seems that printf("%#x", 0)
outputs 0
instead of 0x0
.
Is that normal ?
Thanks !
If buffer size is unified, it is possible to reuse _ntoa_format
for %f
padding and sign generation, saving some code.
Is it good idea/worth it?
Hii~~
This time the types are all right and pwoper,
but nullptr isn't anything in C.
Can we have NULL pwease?
printf.c: In function 'printf':
printf.c:650:41: error: 'nullptr' undeclared (first use in this function)
const int ret = _vsnprintf(_out_char, nullptr, (size_t)-1, format, va);
Hi,
What is missing exactly to support double
numbers ?
I see the message in the tests :
Lines 1016 to 1017 in 21a282a
Maybe for the moment, a message can be added to the caveats in the README :
Double precision is not supported and will result in an empty string.
Thanks !
Hi, excellent lib, clean and sober code, really good this is. Now.
I am wondering if you could consider cpp header only version?
Yes, I know that would be a "point-of-no-return" to C ..
C11 says "if a precision is specified, the 0 flags is ignored" for various integer conversion specifiers.
Test cases:
printf("%02.0u", 0);
=>
printf("%02.0d", 0);
=>
There is minor problem with %g
specifier:
The ‘%g’ and ‘%G’ conversions print the argument in the style of ‘%e’ or ‘%E’ (respectively) if the exponent would be less than -4 or greater than or equal to the precision; otherwise they use the ‘%f’ style. A precision of 0, is taken as 1. Trailing zeros are removed from the fractional portion of the result and a decimal-point character appears only if it is followed by a digit.
printf("%0-15.3g", -42.);
:
"-42.0 "glibc printf:
"-42 "
It seems to me that %g
should use shortest representation, precision is only upper limit, #
modifier may be used if decimal point is requested (unimplemented now)
(https://stackoverflow.com/questions/30658919/the-precision-of-printf-with-specifier-g)
Is it possible that you also define the vsnprintf function for public usage?
I use your lib in combination with https://github.com/wonder-mice/zf_log and therefore i need a vsnprintf implementation.
Hi!
Some placeholder should be used for NULL %s argument to prevent printf from crash:
case 's' : {
const char* p = va_arg(va, char*);
if (!p) p = "(null)"; // please add this
Repeating yourself is generally bad and can easily lead to bugs when updating software. I suggest to change code in functions like _ntoa_long_long
:
char buf[PRINTF_NTOA_BUFFER_SIZE];
....
do {
...
} while (len < PRINTF_NTOA_BUFFER_SIZE);
to:
char buf[PRINTF_NTOA_BUFFER_SIZE];
....
do {
...
} while (len < (sizeof(buf) / sizeof(buf[0])));
or if typing twice sizeof
is too much, you may do:
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
and then
} while (len < ARRAY_SIZE(buf));
Specially important if this is automotive ready code.
This came from this reddit thread https://web.archive.org/web/20200109232034/https://www.reddit.com/r/RISCV/comments/eme6qb/gcc_error_undefined_reference_to_umodsi3/
If the computer architecture does not include remainder, divide, or multiply instructions or software implementations this does not compile. Such instance would be a processor that only implements rv32I instruction set as seen in the reddit thread.
On latest libc's the %zd
specifier can be used to print size_t
.
Interesting edge case:
printf("%.1f", -.95); -> "00000000000000000000000000000010"
Dear mpaland,
hi, i am using ur printf in Arm process and RiscV lib, to get the "printf " function with mimimum lib file
but i cannot get the stdio and stdarg in your code; so far as too many stdio and starg versions : MS VC6.0 /VC2.0 and so on.
so i'd like to get stdio/stdarg from the your repo in github
thank you!
For fctprintf
to be more useful, it would be great if it had a void pointer argument to customize what the given callback function actually relates to, e.g. with regard to a specific stream instance.
Right now, only a statically defined function can be passed to fctprintf
which must behave identical (bar global variables which would defeat the whole purpose of having the callback) each call.
Think about the following signature and call examples:
int fctprintf(void (*out)(char character, void *user), void *user, const char *format, ...);
struct Stream
{
// ...
};
void do_out(char character, void *user)
{
Stream *stream = (Stream *)user;
// Output character on `stream`.
}
Stream stream_1;
Stream stream_2;
fctprintf(do_out, (void *)&stream_1, "%s", "Hello 1");
fctprintf(do_out, (void *)&stream_2, "%s", "Hello 2");
sprintf("%-10.6d", 1024);
Expected: "001024 "
Result: "1024 "
For consideration: Maybe it would be useful to change %p format to be compatible with libc:
In the GNU C Library, non-null pointers are printed as unsigned integers, as if a ‘%#x’ conversion were used. Null pointers print as ‘(nil)’. (Pointers might print differently in other systems.)
Or make this compile-time configurable ...
An indirect precision (as in .*
) is passed as a signed int. But this code interprets it as unsigned. C11 even specifies that negative precision is taken as if the precision were omitted
.
Test case: printf("%.*d", -1, 1)
should output 1
.
The "float" test suite contains the following assertions:
test::sprintf(buffer, "%.9f", 42.8952);
REQUIRE(!strcmp(buffer, "42.895200000"));
test::sprintf(buffer, "%.10f", 42.895223);
REQUIRE(!strcmp(buffer, "42.895223000"));
The first assertion looks correct, but the second assertion appears to be wrong: the precision is set to 10 and there are only 9 digits after the decimal point.
I've checked this with GLIBC 2.23-0ubuntu10 and indeed, 10 digits appear after the decimal point.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.