Arduino and decimal numbers - Page 1
Forum section: electronics / general

author: Red Squirrel
author_rank: Super Contributor
timestamp: July 03, 2015, 07:16:36 am
content: I'm working on a mini project that will require to display numerical info on a LCD (volts and amps). To keep things clean I want the number to always take up the same amount of digits. Going for 0.00 format where decimal point can change. So if the value is 0.1 It will display 0.10, if it's 22 it will display 22.0 etc... A value over 1000 would simply show 999 but it's not likely to reach that far as that would involve magic smoke assuming the readings are actually right.Is there an easy way to do this, or do I have code the logic myself using string manipulation?If it matters, I'm coding the MCU directly using Arduino software/libs and not actually using an arduino board.

author: Mr.B
author_rank: Unknown
timestamp: July 03, 2015, 07:21:29 am
content: Quote from: Red Squirrel on July 03, 2015, 07:16:36 amAFAIK, yes, this is the only way to do it in Arduino.Case statements and string manipulation.Edit: thinking in wrong language... Switch statements...

author: obiwanjacobi
author_rank: Super Contributor
timestamp: July 03, 2015, 09:16:19 am
content: Perhaps you find my TextWriter class useful? It does not do as you describe but it may 'inspire' you...http://atl.codeplex.com/SourceControl/latest#Source/Code/ArduinoTemplateLibrary/TextWriter.h

author: hamster_nz
author_rank: Super Contributor
timestamp: July 03, 2015, 10:05:25 am
content: You could always put it into a 999.99 format field, and then start displaying from a different place depending on if there are any leading zeros.Code:char *start_from;char string; // Allow an extra byte for terminating null char// This can be done smarter, and depends on the data type for 'a'// I'm incorrectly assuming it is a floating point number.string = (a/100)%10 + '0';string = (a/10)%10 + '0';string = (a/1)%10 + '0';string = '.';string = (a*10)%10 + '0';string = (a*100)%10 + '0';// Work out where to display fromif(string = '0')start_from = string+0;else if(string = '0')start_from = string+1;elsestart_from = string+2;// Add a null terminator to the string.start_from = ''\0';output_to_the_display(start_from);

author: f1rmb
author_rank: Regular Contributor
timestamp: July 03, 2015, 12:06:01 pm
content: Hi,you could use the undocumented function dtostrf() (it's part of AVR, check stdlib.h).Cheers.---Daniel

author: senso
author_rank: Frequent Contributor
timestamp: July 03, 2015, 12:56:03 pm
content: Wont printf be able to do that with the formating options?

author: MarkF
author_rank: Super Contributor
timestamp: July 07, 2015, 01:03:39 am
content: If you're programming in C, just do a sprintf command:Code:float value;char line;// set value...// format for output (use just "%.2f" for no leading zero)sprintf(line,"%0.2f",value);// send line to the display...

author: fake-name
author_rank: Regular Contributor
timestamp: July 07, 2015, 01:26:41 am
content: Quote from: f1rmb on July 03, 2015, 12:06:01 pmWhat part of it's undocumented? It's not documented in the arduweenie stuff, but their documentation is garbage anyways.

author: miguelvp
author_rank: Super Contributor
timestamp: July 07, 2015, 01:30:35 am
content: Regarding printf and sprintf.I guess if you can take the about 2KB overhead of the function it would be a solution, but not everyone wants to waste that much space for just printing some characters.Maybe you have space now, but what happens when your program needs to do more?

author: MarkF
author_rank: Super Contributor
timestamp: July 07, 2015, 01:49:54 am
content: Quote from: miguelvp on July 07, 2015, 01:30:35 amIf you want to do everything in integers, keep all your values * 100. Then when you want to display them, divide by 100 to get the integer part and do a modulo 100 to get the decimal part.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 07, 2015, 02:05:32 am
content: Quote from: MarkF on July 07, 2015, 01:03:39 amtest1() performs the conversion, test2() uses sprintf():Code:#include <stdio.h>void test1(float value) {char *start_from;char string; // Allow an extra byte for terminating null charunsigned a;// 'a' is 100*value, but restricted in rangeif(value > 999.0) a = 99999;else if(value < 0) a = 0;else a = value * 100;// Extract the digitsstring = a%10 + '0'; a /= 10;string = a%10 + '0'; a /= 10;string = '.';string = a%10 + '0'; a /= 10;string = a%10 + '0'; a /= 10;string = a%10 + '0';string = ' ';// Work out where to display fromstart_from = string;if(string != '0')start_from += 0; // Leading space, 3 digitselse if(string != '0')start_from += 2; // Two digits, one decimalelsestart_from += 3; // One digit, two decimals// Add a terminatorstart_from = '\0';// Display itprintf("Test1 is '%s'\r\n",start_from);}void test2(float a){char line;sprintf(line,"%0.2f",a);printf("Test2 is '%s'\r\n",line);}int main(int argc, char *argv[]){float value = 0.1;printf("Input is %f\r\n",value);test1(value);test2(value);puts("\r\n");value = 22.0;printf("Input is %f\r\n",value);test1(value);test2(value);puts("\r\n");value = 100.0;printf("Input is %f\r\n",value);test1(value);test2(value);puts("\r\n");value = 123456789.0;printf("Input is %f\r\n",value);test1(value);test2(value);puts("\r\n");value = -1.0;printf("Input is %f\r\n",value);test1(value);test2(value);puts("\r\n");}And the output:Code:Input is 0.100000Test1 is '0.10'Test2 is '0.10'Input is 22.000000Test1 is '22.0'Test2 is '22.00'Input is 100.000000Test1 is ' 100'Test2 is '100.00'Input is 123456792.000000Test1 is ' 999'Test2 is '123456792.00'Input is -1.000000Test1 is '0.00'Test2 is '-1.00'Also, using sprintf() just once will increase your program's code size quite a bit, but if you using it elsewhere then why not...

author: miguelvp
author_rank: Super Contributor
timestamp: July 07, 2015, 02:06:27 am
content: Quote from: MarkF on July 07, 2015, 01:49:54 amI wasn't talking about support for %f but just using sprintf even without float support will add around 2KB to your program. Maybe that's not an issue but it could be one.And if you just want to print some numbers, why do you want to carry the sprintf extra baggage?

author: f1rmb
author_rank: Regular Contributor
timestamp: July 07, 2015, 04:04:44 am
content: Quote from: fake-name on July 07, 2015, 01:26:41 amWhat part ? Seriously ?daniel@daniel-ThinkPad-X201 ~ $ man dtostrfNo manual entry for dtostrfSince when "dtostrf" or "dtostre" are C standard (stdlib) . It's an AVRism, and if you don't jump reading stdlib.h to see the function prototype, no way you miraculously think of it.BTW, nice and useful commentTo the OP, I share the same advice, try to avoid sprintf() if possible, it's a memory hog.Cheers.---Daniel

author: miguelvp
author_rank: Super Contributor
timestamp: July 07, 2015, 04:19:42 am
content: Don't look, kind of looks like documentation to me:http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdlib_1ga060c998e77fb5fc0d3168b3ce8771d42.htmlJust because it's not on your man pages doesn't mean it's undocumented

author: f1rmb
author_rank: Regular Contributor
timestamp: July 07, 2015, 04:33:51 am
content: Quote from: miguelvp on July 07, 2015, 04:19:42 amOnce again, it's not a C standard function but an AVRism, unlike *printf(), or did I missed something for the last 25 years ?If it's not in man pages, it doesn't even existsCheers.---Daniel

author: miguelvp
author_rank: Super Contributor
timestamp: July 07, 2015, 04:48:24 am
content: hmm, undocumented to me means there is no documentation available, and nothing to do with being part of the C standard libraries.I always thought that even standard C libraries are not really part of C, since they are just libraries, but hey, when in Rome!

author: SL4P
author_rank: Super Contributor
timestamp: July 07, 2015, 05:14:34 am
content: For all the hate levelled atprintf()and it's derivatives - I love it, as it let's me get the output results I want i a convenient and standardized manner. decimals, justification, fill - everything you want today or have wanted for the last 30 years!Yes it occupies memory, and if you want to do just one specific format - write your own output function - otherwise stop complaining!

author: Red Squirrel
author_rank: Super Contributor
timestamp: July 07, 2015, 10:02:11 am
content: Thanks for the ideas, I'll play around with those.Goal is to probably use ints throughout most of the program, given the ADC produces a value from 1 to 1024 it's probably easier for me to times it by a certain value so I get a number in the 10,000's then just add a decimal point at the right spot. I read somewhere that division is slower on MCUs so to try to avoid it. Probably not a big issue for this particular app but may as well try to use good habits. I'll try to avoid springf as well.Come to think of it all I really need to do here is get the value converted to a string, then I can do a char by char analsys to decide what digits to display and where to put to decimal.

author: SL4P
author_rank: Super Contributor
timestamp: July 07, 2015, 10:21:39 am
content: Quote from: Red Squirrel on July 07, 2015, 10:02:11 amKeep in mind that bit shifting / rotating is very fast, and you can do quite a lot of maths with combinations of the above... also as you said - think (integer x 1xxx), then plonk a decimal back where it belongs!

author: MarkF
author_rank: Super Contributor
timestamp: July 07, 2015, 11:22:22 am
content: Quote from: SL4P on July 07, 2015, 10:21:39 amMost modern compilers know this too. Don't try to out smart them. Write your code the most straight forward way and then look at the generated assembly if you need to try to optimize it yourself.

author: westfw
author_rank: Super Contributor
timestamp: July 08, 2015, 07:34:50 am
content: QuoteI'll point out that this is NOT the desired output. I haven't done a lot with floating point printf, but I couldn't immediately come up with a printf format that WOULD produce the correct results. (Nor a fortran format, either :-))Now that several people have suggested a format that doesn't work, I guess I'll ask explicitly: IS there a format that will produce those outputs? IIRC, the OP wanted:0.010.223.3344.455566669999

author: MarkF
author_rank: Super Contributor
timestamp: July 08, 2015, 08:02:08 am
content: Quote from: westfw on July 08, 2015, 07:34:50 amShort answer: NO.Long answer: He would need to check the magnitude of the variable before doing the format.Code:float val;char txt;if (val < 10.0) {sprintf(txt,"%4.2f",val); // format with 2 decimal digits}else if (val < 100.0) {sprintf(txt,"%4.1f",val); // format with 1 decimal digit}else if (val < 1000.0 {sprintf(txt,"%4.0f",val); // format with 0 decimal digits}else {sprintf(txt," 999"); // display 999 if greater than or equal to 1000}

author: hamster_nz
author_rank: Super Contributor
timestamp: July 08, 2015, 10:11:42 am
content: Quote from: MarkF on July 08, 2015, 08:02:08 amAnnoying corner case time... 99.99 is less than 100.0 ,but will produce a 5 character output with a "%4.1f" format due to rounding. Grrr..Code:$ cat a.c#include <stdio.h>int main(int argc, char *argv){printf("%4.1f\r\n",(float)99.5);printf("%4.1f\r\n",(float)99.99);}$ gcc -o a a.c ; ./a$ ./a99.5100.0so you also need to use limits that allow for the effects of rounding.Also, with an embedded display when you jump form "9.99" to "10.0" and back again rapidly the display can become hard to read. This canbe really non-trivial to solve.

author: MarkF
author_rank: Super Contributor
timestamp: July 08, 2015, 05:23:39 pm
content: I guess the easiest way would be to truncate the value before formatting it:Code:void toAscii(int x, int y, float val){char txt;if (val < 10.)val = floor(val*100.)/100.;else if (val < 100.)val = floor(val*10.)/10.;elseval = floor(val);if (val < 10.)sprintf(txt,"%4.2f",val);else if (val < 100.)sprintf(txt,"%4.1f",val);else if (val < 1000.)sprintf(txt,"%4.0f",val);elsesprintf(txt," 999");drawTextToDisplay(x,y,txt);}

author: tszaboo
author_rank: Super Contributor
timestamp: July 08, 2015, 05:32:46 pm
content: Quote from: miguelvp on July 07, 2015, 02:06:27 amMost code for Arduino will contain printf for debugging reason anyway, so I'm not sure why would be a problem to use it. The whole reason for using a Arduino is to reduce the time required to connect the stuff and write the code, when efficiency is irrelevant.

author: miguelvp
author_rank: Super Contributor
timestamp: July 08, 2015, 06:42:05 pm
content: In that case, then use * for width and .* for precision so you can specify them on the flyi.e.sprintf(txt, "%*.*f", width, precision, val);computing width and precision should be easy enough.I think Arduino's printf doesn't support float formatting to save space by default, but there is a way to enable that functionality.

author: f1rmb
author_rank: Regular Contributor
timestamp: July 08, 2015, 07:14:54 pm
content: Yeah, there is no float support, this is why dtostrf() exists.Also, float are double on AVR.Cheers.---Daniel

author: alank2
author_rank: Super Contributor
timestamp: July 09, 2015, 12:03:01 am
content: I have a library for AVR gcc that is a printf that will allow you to specify the decimal places. An integer of 123 with decimal places would be printed to a string as "1.23".http://www.avrfreaks.net/projects/decimal-integer-sprintf-library