0.2.2 printHelpers

This commit is contained in:
rob tillaart 2021-11-13 18:15:22 +01:00
parent 26346eb80a
commit 295c1d6728
9 changed files with 184 additions and 153 deletions

View File

@ -2,6 +2,10 @@ compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
- leonardo
- due
- zero
- leonardo
- m4
- esp32
- esp8266
- mega2560

View File

@ -4,10 +4,14 @@ name: Arduino CI
on: [push, pull_request]
jobs:
arduino_ci:
runTest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: Arduino-CI/action@master
# Arduino-CI/action@v0.1.1
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6
- run: |
gem install arduino_ci
arduino_ci.rb

View File

@ -1,10 +1,11 @@
[![Arduino CI](https://github.com/RobTillaart/printHelpers/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)
[![Arduino-lint](https://github.com/RobTillaart/printHelpers/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/printHelpers/actions/workflows/arduino-lint.yml)
[![JSON check](https://github.com/RobTillaart/printHelpers/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/printHelpers/actions/workflows/jsoncheck.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/printHelpers/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/RobTillaart/printHelpers.svg?maxAge=3600)](https://github.com/RobTillaart/printHelpers/releases)
# printHelpers
Arduino library to help formatting data for printing
@ -17,19 +18,22 @@ data in a way not possible in the standard print library of the Arduino.
The following functions are implemented:
- **print64(int64_t, base)** converts a 64 bit integer number to a char array.
- **char \* print64(int64_t value, uint8_t base)** converts a 64 bit integer
number to a char array.
The plus sign is not printed, neither are leading zero's.
Base 10 (DEC) and 16 (HEX) are supported and other bases up to 36 can be used.
Note that negative numbers will always get a minus sign for any base.
Cast the number to uint64_t to suppress the sign.
- **print64(uint64_t, base)** converts a unsigned 64 bit int number to a char array.
- **char \* print64(uint64_t value, uint8_t base)** converts a unsigned 64 bit
int number to a char array.
No sign is printed, neither are leading zero's.
Base 10 (DEC) and 16 (HEX) are supported and bases up to 36 can be used.
----
- **sci(double, decimals)** converts a float or double to a char array.
- **char \* sci(double value, uint8_t decimals)** converts a float or double to a
char array.
E.g. print(sci(f, 4)) ==> results in "6.7407E+21".
The existing Arduino print library only supports printing of floats and
doubles up toabout 4E9 while the range of floats goes up to ~1E38.
@ -38,15 +42,17 @@ support down to about 1E-38 (subnormal even to 1E-45).
Exisiting library functions **dtostrf()** has no scientific notation
and **dtostre()** (AVR) is limited to 7 decimals.
- **eng(double, decimals)** converts a float or double to a char array.
- **char \* eng(double value, uint8_t decimals)** converts a float or double to a
char array.
E.g. print(eng(f, 4)) ==> results in "6.7407E+21".
Note the exponent created by **eng()** is always a multiple of 3.
- **scieng(double, decimals, expmultiple)** converts a float or double to a char array.
- **char \* scieng(double value, uint8_t decimals, uint8_t exponentMultiple)** converts a
float or double to a char array.
**sci()** and **eng()** use the same underlying function called **scieng()**
as the initial code for converting was almost identical.
Although not intended to be used directly, one can use it.
The last parameter expmultiple defines where the exponent is a multiple of.
The last parameter **exponentMultiple** defines where the exponent is a multiple of.
For the **sci()** function this is 1, for the **eng()** function this is 3.
The **scieng()** function works for multiples from 1..9 for the exponent.
The usability of other values than 1 and 3 are not known.
@ -55,7 +61,7 @@ mantissa.
----
- **toBytes(val, decimals = 2)** makes from a big number
- **char \* toBytes(double value, uint8_t decimals = 2)** makes from a big number
representing an amount of bytes a shorter string usable for displaying.
The number of decimals is max 3
Example 3.292.528 ==> "3.140MB"
@ -68,7 +74,7 @@ List of prefixes:
treda Byte == TDB uses 2 chars to indicate the magnitude so that would
take extra memory of more complex code.
As it is seldom used, "official" support stops with UDA.
Should be big enough for some time.
Should be big enough for some time.
To have some support the code uses lowercase for the next 8 levels:
treda sorta rinta quexa pepta ocha nena minga luma (1024\^21 ~~ 10\^63)
@ -79,37 +85,45 @@ More formatting functions might be added in the future.
## Shared buffer
The implementation of the function all use a shared buffer to hold the generated string.
This is done to reduce the memory overhead of embedding static buffers. **Note this is not thread safe!**
In a coming release the functions will be able to pass a buffer to them to become more thread safe.
The implementation of the function all use a shared buffer to hold the
generated string.
This is done to reduce the memory overhead of embedding static buffers.
**Note this is not thread safe!**
In a coming release the functions will be able to pass a buffer to them
to become more thread safe.
The size of this shared buffer is default 66 to be able to print a 64 bit integer in base 2.
To save memory one can change this buffersize in the code or compile time by changing **PRINTBUFFERSIZE**.
The size of this shared buffer is default 66 to be able to print a 64 bit
integer in base 2.
To save memory one can change this buffer size in the code or compile time
by changing **PRINTBUFFERSIZE**.
Be aware that **sci()** and **eng()** use the same buffer.
These need about 10 bytes plus one bytes for every decimal used.
So for floats one need 15-20 bytes max, for doubles one need up to 30 bytes max.
In practice a size of 22 will work for most applications.
| PRINTBUFFERSIZE | BASE SUPPORTED | nr. decimals |
|:----:|:----:|:----:|
| 66 | 02 - 36 | 0 - 50 |(default)
| 34 | 04 - 36 | 0 - 20 |
| 24 | 08 - 36 | 0 - 14 |
| 22 | 10 - 36 | 0 - 12 |
| 18 | 16 - 36 | 0 - 07 |
| PRINTBUFFERSIZE | BASE SUPPORTED | nr. decimals | Notes |
|:---------------:|:--------------:|:------------:|:----------|
| 66 | 02 - 36 | 0 - 50 | (default) |
| 34 | 04 - 36 | 0 - 20 |
| 24 | 08 - 36 | 0 - 14 |
| 22 | 10 - 36 | 0 - 12 |
| 18 | 16 - 36 | 0 - 07 |
When functions are added, the recommended minimum size might increase.
## Future
- Investigate the precision of **sci()** and **eng()**.
- Investigate performance of **sci()** and **eng()**.
- Add option to pass char buffer as parameter (improve threadsafe)
- Add more print helpers.
## Operation
See examples
## Future
- Investigate the precision of **sci()** and **eng()**.
- Investigate performance of **sci()** and **eng()**.
- Investigate performance (local variables iso modifying parameters)
- Add option to pass char buffer as parameter (improve threadsafe)
- Add more print helpers.
- improve readability of the code (even more)
- check TODO's in the code
-

View File

@ -1,7 +1,7 @@
//
// FILE: sci_test.ino
// AUTHOR: Rob Tillaart
// VERSION: 0.0.1
// VERSION: 0.1.0
// PURPOSE: test different values with sci function
@ -153,6 +153,7 @@ void test5()
Serial.println();
}
void test6()
{
if (sizeof(double) >= 8)
@ -195,3 +196,4 @@ void test6()
// -- END OF FILE --

View File

@ -1,13 +1,19 @@
# Syntax Coloring Map for printHelpers
# Syntax Colouring Map for printHelpers
# Data types (KEYWORD1)
# Datatypes (KEYWORD1)
# Methods and Functions (KEYWORD2)
print64 KEYWORD2
sci KEYWORD2
eng KEYWORD2
scieng KEYWORD2
toBytes KEYWORD2
# Constants (LITERAL1)
# Constants (LITERAL1)
PRINTHELPERS_VERSION LITERAL1
PRINTBUFFERSIZE LITERAL1

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/printHelpers"
},
"version": "0.2.1",
"version": "0.2.2",
"license": "MIT",
"frameworks": "arduino",
"platforms": "*"

View File

@ -1,5 +1,5 @@
name=printHelpers
version=0.2.1
version=0.2.2
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino library to help formatting data for printing. 64 bit integers (base 10 and 16). Engineering and scientific notation.

View File

@ -3,7 +3,7 @@
// FILE: printHelpers.h
// AUTHOR: Rob Tillaart
// DATE: 2018-01-21
// VERSION: 0.2.1
// VERSION: 0.2.2
// PUPROSE: Arduino library to help formatting for printing.
// URL: https://github.com/RobTillaart/printHelpers
@ -12,7 +12,7 @@
#include "stdlib.h"
#define PRINTHELPERS_VERSION (F("0.2.1"))
#define PRINTHELPERS_VERSION (F("0.2.2"))
// 24 is a pretty safe minimum
@ -38,29 +38,29 @@ char __printbuffer[PRINTBUFFERSIZE];
// buffer size 24 will work for base 8 -36
// buffer size 22 will work for base 10 - 36
char * print64(int64_t n, uint8_t base = 10)
char * print64(int64_t value, uint8_t base = 10)
{
char * buf = __printbuffer;
char * buffer = __printbuffer;
uint8_t i = 0;
uint8_t j = 0;
buf[0] = 0;
buffer[0] = 0;
// small base need bigger buffer
if ((base < 10) && (PRINTBUFFERSIZE <= 22)) return buf;
if ((base < 10) && (PRINTBUFFERSIZE <= 22)) return buffer;
// handle special case
if (n == 0)
if (value == 0)
{
buf[0] = '0';
buf[1] = 0;
return buf;
buffer[0] = '0';
buffer[1] = 0;
return buffer;
}
// PREFIX NEGATIVE
// handle negative values (for all bases for now)
if ((n < 0) && (base != 16))
if ((value < 0) && (base != 16))
{
n = -n;
buf[0] = '-';
value = -value;
buffer[0] = '-';
i++;
j++;
}
@ -68,71 +68,71 @@ char * print64(int64_t n, uint8_t base = 10)
// PREFIX HEX
if (base == 16)
{
buf[0] = '0';
buf[1] = 'x';
buf[2] = 0;
buffer[0] = '0';
buffer[1] = 'x';
buffer[2] = 0;
i = 2;
j = 2;
}
// create one digit per loop
while (n > 0)
while (value > 0)
{
int64_t t = n / base;
uint8_t p = n - t * base;
buf[i++] = (p < 10) ? '0' + p : ('A' - 10) + p;
n = t;
int64_t temp = value / base;
uint8_t digit = value - temp * base;
buffer[i++] = (digit < 10) ? '0' + digit : ('A' - 10) + digit;
value = temp;
}
buf[i] = 0;
buffer[i] = 0;
// reverse buffer
--i;
while ( i > j)
{
uint8_t t = buf[i];
buf[i] = buf[j];
buf[j] = t;
uint8_t temp = buffer[i];
buffer[i] = buffer[j];
buffer[j] = temp;
i--;
j++;
}
return buf;
return buffer;
}
char * print64(uint64_t n, uint8_t base = 10)
char * print64(uint64_t value, uint8_t base = 10)
{
char * buf = __printbuffer;
char * buffer = __printbuffer;
uint8_t i = 0;
uint8_t j = 0;
buf[0] = 0;
buffer[0] = 0;
// small base need bigger buffer
if ((base < 10) && (PRINTBUFFERSIZE <= 22)) return buf;
if ((base < 10) && (PRINTBUFFERSIZE <= 22)) return buffer;
// handle special case
if (n == 0)
if (value == 0)
{
buf[0] = '0';
buf[1] = 0;
return buf;
buffer[0] = '0';
buffer[1] = 0;
return buffer;
}
// create one digit per loop
while (n > 0)
// create one digit per iteration
while (value > 0)
{
uint64_t t = n / base;
uint8_t p = n - t * base;
buf[i++] = (p < 10) ? '0' + p : ('A' - 10) + p;
n = t;
uint64_t temp = value / base;
uint8_t digit = value - temp * base;
buffer[i++] = (digit < 10) ? '0' + digit : ('A' - 10) + digit;
value = temp;
}
buf[i] = 0;
// reverse buf
buffer[i] = 0;
// reverse buffer
--i;
while (i > j)
{
uint8_t t = buf[i];
buf[i] = buf[j];
buf[j] = t;
uint8_t temp = buffer[i];
buffer[i] = buffer[j];
buffer[j] = temp;
i--;
j++;
}
return buf;
return buffer;
}
@ -143,9 +143,10 @@ char * print64(uint64_t n, uint8_t base = 10)
// typical buffer size for 8 byte double is 22 bytes
// 15 bytes mantissa, sign dot E-xxx
char * scieng(double number, uint8_t digits, uint8_t em)
// em = exponentMultiple.
char * scieng(double value, uint8_t decimals, uint8_t em)
{
char * buf = __printbuffer;
char * buffer = __printbuffer;
int exponent = 0;
int pos = 0;
double e1 = 10;
@ -158,107 +159,107 @@ char * scieng(double number, uint8_t digits, uint8_t em)
// Handling these costs 13 bytes RAM
// shorten them with N, I, -I ?
if (isnan(number))
if (isnan(value))
{
strcpy(buf, "nan");
return buf;
strcpy(buffer, "nan");
return buffer;
}
if (isinf(number))
if (isinf(value))
{
if (number < 0) strcpy(buf, "-inf");
strcpy(buf, "inf");
return buf;
if (value < 0) strcpy(buffer, "-inf");
strcpy(buffer, "inf");
return buffer;
}
// Handle negative numbers
if (number < 0.0)
if (value < 0.0)
{
buf[pos++] = '-';
number = -number;
buffer[pos++] = '-';
value = -value;
}
// Scale exponent to multiple of em
// TODO: can we remove loop to reduce rounding errors
while (number >= e1)
while (value >= e1)
{
number *= e2;
value *= e2;
exponent += em;
}
// TODO: can we remove loop to reduce rounding errors
while (number < 1 && number != 0.0)
while (value < 1 && value != 0.0)
{
number *= e1;
value *= e1;
exponent -= em;
}
// Round correctly so that print(1.999, 2) prints as "2.00"
double rounding = 0.5;
// TODO: can we remove loop to reduce rounding errors
for (uint8_t i = 0; i < digits; ++i)
for (uint8_t i = 0; i < decimals; ++i)
{
rounding *= 0.1;
}
number += rounding;
if (number >= e1)
value += rounding;
if (value >= e1)
{
exponent += em;
number *= e2;
value *= e2;
}
// Split whole part and remainder
uint32_t d = (uint32_t)number;
double remainder = number - d;
uint32_t d = (uint32_t)value;
double remainder = value - d;
// print whole part
itoa(d, &buf[pos], 10);
pos = strlen(buf);
itoa(d, &buffer[pos], 10);
pos = strlen(buffer);
// print remainder part
if (digits > 0)
if (decimals > 0)
{
buf[pos++] = '.'; // decimal point
buffer[pos++] = '.'; // decimal point
}
// Extract digits from the remainder one at a time
// Extract decimals from the remainder one at a time
// to prevent missing leading zero's
// TODO: can we remove loop to reduce rounding errors
while (digits-- > 0)
while (decimals-- > 0)
{
remainder *= 10;
d = (uint8_t)remainder;
buf[pos++] = d + '0';
buffer[pos++] = d + '0';
remainder -= d;
}
// print exponent
buf[pos++] = 'E';
buffer[pos++] = 'E';
if (exponent < 0)
{
buf[pos++] = '-';
buffer[pos++] = '-';
exponent = -exponent;
}
else buf[pos++] = '+';
else buffer[pos++] = '+';
itoa(exponent, &buf[pos], 10);
return buf;
itoa(exponent, &buffer[pos], 10);
return buffer;
}
char * eng(double number, uint8_t digits)
char * eng(double value, uint8_t decimals)
{
return scieng(number, digits, 3);
return scieng(value, decimals, 3);
}
char * sci(double number, uint8_t digits)
char * sci(double value, uint8_t decimals)
{
return scieng(number, digits, 1);
return scieng(value, decimals, 1);
}
void sci(Stream &str, double f, uint8_t digits)
void sci(Stream &str, double value, uint8_t decimals)
{
str.print(sci(f, digits));
str.print(sci(value, decimals));
}
////////////////////////////////////////////////////////////
@ -271,60 +272,60 @@ void sci(Stream &str, double f, uint8_t digits)
// zetta yotta xona weka vunda uda (1024^12)
//
// (treda Byte == TDB is the next one and it is 2 char
// so codewise difficult and as it is seldom used, support stops there.
// so code wise difficult and as it is seldom used, support stops there.
//
// To have some support the code uses lowercase for the next 8 levels
// treda sorta rinta quexa pepta ocha nena minga luma (1024 ^21 ~~ 10^63)
char * toBytes(double val, uint8_t decimals = 2)
char * toBytes(double value, uint8_t decimals = 2)
{
static char buf[12];
char t[] = " KMGTPEZYXWVUtsrqponml";
uint8_t i = 0;
if (isinf(val))
static char buffer[12];
char t[] = " KMGTPEZYXWVUtsrqponml";
uint8_t i = 0; // i is index of the array == powers of 1024.
if (isinf(value))
{
strcpy(buf, "<inf>");
return buf;
strcpy(buffer, "<inf>");
return buffer;
}
while(val >= 1024)
while(value >= 1024)
{
val /= 1024;
value /= 1024;
i++;
}
if (i == 0) decimals = 0;
if (decimals > 3) decimals = 3;
// WHOLE PART
int iv = val;
itoa(iv, &buf[0], 10);
// WHOLE PART iv
int integerPart = value;
itoa(integerPart, &buffer[0], 10);
// DECIMALS
val -= iv;
uint8_t pos = strlen(buf);
value -= integerPart;
uint8_t pos = strlen(buffer);
if (decimals > 0)
{
buf[pos++] = '.';
buffer[pos++] = '.';
while (decimals-- > 0)
{
val = val * 10;
buf[pos++] = '0' + int(val);
val -= int(val);
value = value * 10;
buffer[pos++] = '0' + int(value);
value -= int(value);
}
}
// UNITS
if (i <= strlen(t))
{
if (i > 0) buf[pos++] = ' ';
buf[pos++] = t[i];
buf[pos++] = 'B';
buf[pos] = 0;
if (i > 0) buffer[pos++] = ' ';
buffer[pos++] = t[i];
buffer[pos++] = 'B';
buffer[pos] = 0;
}
else
{
// TODO e.g. E99 B
}
return buf;
return buffer;
}

View File

@ -48,7 +48,7 @@ unittest_teardown()
unittest(test_sci)
{
fprintf(stderr, "VERSION: %s\n", PRINTHELPERS_VERSION);
fprintf(stderr, "VERSION: %s\n", (char *) PRINTHELPERS_VERSION);
fprintf(stderr, "PRINTBUFFERSIZE: %d\n", PRINTBUFFERSIZE);
fprintf(stderr, "%s\n", sci(PI * 1000, 6));
@ -66,7 +66,7 @@ unittest(test_sci)
unittest(test_eng)
{
fprintf(stderr, "VERSION: %s\n", PRINTHELPERS_VERSION);
fprintf(stderr, "VERSION: %s\n", (char *) PRINTHELPERS_VERSION);
fprintf(stderr, "PRINTBUFFERSIZE: %d\n", PRINTBUFFERSIZE);
int32_t value32 = 1UL << 25;
@ -87,7 +87,7 @@ unittest(test_eng)
unittest(test_print64)
{
fprintf(stderr, "VERSION: %s\n", PRINTHELPERS_VERSION);
fprintf(stderr, "VERSION: %s\n", (char *) PRINTHELPERS_VERSION);
fprintf(stderr, "PRINTBUFFERSIZE: %d\n", PRINTBUFFERSIZE);
int64_t value64 = 1ULL << 35;
@ -103,7 +103,7 @@ unittest(test_print64)
unittest(test_toBytes)
{
fprintf(stderr, "VERSION: %s\n", PRINTHELPERS_VERSION);
fprintf(stderr, "VERSION: %s\n", (char *) PRINTHELPERS_VERSION);
for (int i = 0; i < 30; i++)
{