Chris--A / PrintEx

An extension to the Arduino Print library, and much, much more...
GNU General Public License v3.0
61 stars 16 forks source link

Feature request: Pseudo floating point numbers using integer data types #30

Open hattesen opened 5 years ago

hattesen commented 5 years ago

Proposed feature

I propose, that the printf precision option be made to also work with pseudo floating point numbers, represented by an integer type with an implicit (fixed) decimal exponent.

Example:

The value 3.14 could be represented (with two decimals precision) by an integer value of 314 (assuming an implicit decimal exponent of -2): 314 * 10^(-2) = 3.14

Note, that precision = -exponent

Current printf formatting design.

Proposed behavior

Using the standard "f" specifier, with an implicit float conversion: int_value * 10^(-precision)

printf("Pi = %.2f", 314); // result: "pi = 3.14"
printf("Amount $%.2f", 23456); // result: "Amount $234.56

It may be a good idea, to even support negative precision specifiers (positive decimal exponents):

printf("Rounded %.-2f", (byte) 123); // result: "Rounded 12300"

For backwards compatibility, it might be better to use a new "integer with decimal exponent" specifier, rather than repurposing the "f" specifier with an integer argument.

Motivation

On memory and CPU resource restricted MCUs like the AVRs used on the Arduino, it is common practice to avoid using floats to represent floating point values, like temperature, but instead use an integer data type, like an int, representing tenth of degrees. So a temperature of 45.6 would be represented by an integer value of 456, with an implicit decimal exponent (10^-2). Similarly, a monetary amount, e.g. $234.56 can be represented by an integer value, representing the number of cents: 23456, with an implicit decimal exponent (10^-2)

Such an "external/fixed 10^n exponent" implementation have several advantages over using native floating point data types:

scottchiefbaker commented 5 years ago

I understand the desire for this on AVRs, but modifying the f specifier is a fundamental change in printf() behavior and probably best avoided for consistency with other implementations.

I do like the idea however. If we were to create a new modifier specifically for this library that does not conflict with other implementations I would support it. What about W for "wide int"?

hattesen commented 5 years ago

I agree that modifying the f specifier can be problematic.

Doesn't matter much what letter is chosen as the specifier for this "integer with decimal exponent" "decimal integer" or "pseudo float", but reusing the relevant modifiers from the f specifier would be ideal for consistency.

jirkaptr commented 5 years ago

The following solutions may be appropriate:

char*  ltostrqf(long int value, unsigned char prec, char *buff )
{
    char *fmt = "%ld.%Xld";
    fmt[5] = '0' + prec;

    long int divisor = 1; 
    while(prec--)
        divisor *= 10;

    sprintf(buff, fmt, value / divisor, abs(value % divisor));
    return buff;
}

Application examples:

 PrintEx serial = Serial;
 ltostrqf(-987654321, 5, qf_buf);
 serial.printf("***%16s***%5s***\n", qf_buf, qf_buf);
 serial.printf("###%16s###\n", ltostrqf(-1234567890, 3, qf_buf));
 Serial.println(ltostrqf(123456789, 5, qf_buf));

Comment: • The name is chosen to characterize the content ("long -to-string-quasi-float"). The call also corresponds to the similar dtostrf () function with the exception the width parameter.. • Parameter width (or right alignment) is entered in the formating string of printf(), eg. %12s. • Overflow and formal call rules (parameters) checks are not performed. • If 0<precision< 5 is applied anytime then the divisor can be int type. • The function is freely usable independently of the PrintEx library.

scottchiefbaker commented 5 years ago

I think I found a possible bug:

void loop() {
    char qf_buf[50] = "";

    ltostrqf(-987654321 , 5, qf_buf);

    serial.printf("Count up  : %s\n", ltostrqf(123456789, 3, qf_buf));
    serial.printf("Count down: %s\n", ltostrqf(987654321, 3, qf_buf));

    delay(5000);
}

Adding the initial ltostrqf() causes the output on the other two to be wrong. If I comment out the offending line it's correct again.

scottchiefbaker commented 5 years ago

Also, with warnings enabled I get:

warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
   char *fmt  = "%ld.%Xld";

Chaning it to char fmt[] = "%ld.%Xld"; fixes the warning

scottchiefbaker commented 5 years ago

The "bug" may be a simple integer overflow. Here is the full sketch. Compare the output after commenting out line #12.

ltostrqf-bug.txt

jirkaptr commented 5 years ago

Now I understand your problem! You probably did not fix the PrintExtension.h! Apply a fix as stated in #26 . I believe that then everything will be OK.

scottchiefbaker commented 5 years ago

Aha... that would make sense!

jirkaptr commented 5 years ago

Sorry for errors in ltostrqf(). The original version does not process correctly numbers in the target format range of -0.9999999 to +0.999999. I attach a corrected version here: ltostrqf_20190209.txt