General ANSI C: Converting float number to characters

Go To Last Post
81 posts / 0 new

Pages

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

barnacle wrote:
Separate the result into a whole number part and a fraction, then print the two with a decimal point between them

You also need to take care of the sign - the code shown will fail for values between 0 and -1.0 (exclusive)

 

EDIT

 

ie, values less than 0.0 but greater than -1.0

 

EDIT 2

 

From bitter experience: https://www.avrfreaks.net/commen...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Sat. Jun 5, 2021 - 09:57 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

the_real_seebs wrote:

Converting floating point values to characters is very hard. I have seen multiple top-tier C programmers trying, and failing, to debug edge cases in this. (Why weren't they just using printf, you ask? Because we were the library maintainer for the OS, and someone found an edge case bug in our float conversion...)

 

In practice, it is probably the case that you will be better off converting to an integer value at a suitable scale, converting that to digits, and adding a decimal point yourself.

 

And yes, that lcd_writeString function is horrible. It should not continue writing after reaching a NUL.

 

 

That is the craziest experience that I face with C.  When I was a boy, I could do this in Day One with BASIC.  blush

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

If you are in charge of the temperature code, make it return an integer value in x100 so anyone else that deals with the result can do so in integers. It may be your mcu in use at the moment can handle float with ease, but that may not always be the case (assuming you want to reuse code). The more capable mcu will not care if it deals in integers, and the less capable mcu may require it. It may depend on what direction you came from to get to the mcu- if started out with small mcu's, then float is avoided out of habit, if coming from a pc it will appear that float is the only way to do some of these calculations. In any case you use what is 'best' for what is in use, but for something that does not need float precision like temperature it will not hurt to think in integers.

 

Simple example. The only thing to watch for is the %100 which has to be done on the absolute value. The avr has a builtin function for that, the xc8-pic compiler most likely needs to use abs() instead (or equivalent code).

 

#include "MyAvr.hpp"
#include "LcdST7032.hpp"

 

LcdST7032_Twi   lcd     { board.twiPins };
Tmp117          tmp117  { board.twiPins };

 

int main(){

    rtc.init();
    InterruptsOn();

    while(1) {
        waitMS( 1000 );

        // lcd - \t is home+cls, \n is next line
        // lcd ignores anything outside of display
 
        //temp functions return degrees in x100
        auto tC = tmp117.tempCx100();
        lcd.print( "\ttempC: %d.%02u", tC/100, __builtin_abs(tC)%100 );

        auto tF = tmp117.tempFx100();
        lcd.print( "\ntempF: %d.%02u", tF/100, __builtin_abs(tF)%100 );
    }

}

Last Edited: Sun. Jun 6, 2021 - 08:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
In any case you use what is 'best' for what is in use, but for something that does not need float precision like temperature it will not hurt to think in integers.
or fixed-point reals.

PIC32 has fixed-point with saturation; in Embedded C fixed-point, saturation is optional.

 

Data Types - Developer Help

[2/3 page]

Fixed-point Fractional Data Types

TR 18037: Embedded C | Project status and milestones

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

barnacle wrote:
Why do you need floating point printf at all?
Clarity is worth a lot.

printf for floats might be overkill,

but if one has the memory, why not use it?

 

I expect the given code will work as desired.

Some thought was required to convince myself that fraction would never be 100.

A comment to the effect that 1-2**-23 will print as 0.99, not 0.100 would be a good thing.

 

 

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The original question was "General ANSI C: Converting float number to characters"

 

The original answers were about using the Standard C Library functions.  e.g.

sprintf(buf, "%f", fp_expression);

You can specify width, precision, alignment, ...

 

However many AVR toolchains default to a cut-price printf() that does not support floats.

You need to tick a box in Codevision to enable full printf features.

You need to tick boxes and add special libraries to avr-gcc in AS7.0

 

Alternatively avr-gcc provides dtostrf() e.g.

dtostrf(fp_expression, width, prec, buf);

 

In real applications it is more important to have the correct result in a nicely formatted char buf[10];

 

Yes,  of course you can hand-craft your own functions.   Yes,  these can use less memory on a tiny AVR.

An interesting discussion but not really what was asked.

 

In my personal opinion it is always wise to use proven Standard Libraries.

You only go down custom rabbit holes if you run out of memory.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HKPhysicist wrote:
That is the craziest experience that I face with C.  When I was a boy, I could do this in Day One with BASIC

Go on. Using printf is day-one stuff in C.

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

Go on. Using printf is day-one stuff in C.

Yes,  on a regular Platform.

 

But for most AVR toolchains you have to tick the odd box before you can use the full-fat printf()

 

The OP has never mentioned which particular AVR he wants to use.

All that I have observed is that he has used PIC32 (which has much more memory, resources, ...)

 

My advice is:

1.  say which dev board you are using

2.  say which IDE you prefer

3.  say which language you want to use.  e.g. C, C++, BASIC, ASM, ...

4.  say what you want to do.

 

Having chosen (1) and (2) stick to them.   Otherwise you will just get confused.   (and so will Forum members)

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>The OP has never mentioned which particular AVR he wants to use.

 

#32 shows he is using a pic18. Probably nicer to know in #1, but it is now known.

 

The xc8-pic compiler will analyze and grab what it needs to make your use of stdio functions work- if no floats are used, you get no float code. A simple use of snprintf with float is about 15k, without float about half that (with -02).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have a PIC18F458 and a PIC18F14K50 that I might have used 10-15 years ago.

 

From memory,  the PIC peripherals are good,  code generation / compilers is poor.   Neither have much SRAM or Flash.

I presume that all PIC18Fxxx models will suffer the same architectural features.

 

The PIC18F14K50 will be fine for many projects (that don't need many pins)

The PIC18F458 might struggle with Flash space.

 

I am sure that I have used f-p with both of them.   Perhaps I will dig out the PIC18F458 board and display some formatted float values on a LCD.

I suspect that I will run out of memory.

 

And dig out an ATtiny4313 board.   And see how I get on with formatted float values on a LCD.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
You need to tick boxes and add special libraries to avr-gcc in AS7.0
Similar in MPLAB X.

https://github.com/microchip-pic-avr-examples/avr128da48-cnano-printf-float-mplab-mcc#-wl-uvfprintf--lprintf_flt--lm

via https://github.com/microchip-pic-avr-examples/?q=print

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
The xc8-pic compiler will analyze and grab what it needs to make your use of stdio functions work- if no floats are used, you get no float code.
fyi, can format in Data Visualizer instead of on the MCU.

Variable Data Types | Variable Streamers | MPLAB® Data Visualizer due to https://github.com/microchip-pic-avr-examples/?q=print

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
I have a PIC18F458 and a PIC18F14K50 that I might have used 10-15 years ago.

 

From memory,  the PIC peripherals are good,  code generation / compilers is poor.

MPLAB XC8 v2 for PIC is based on Clang (LLVM, 27-May'18)

MPLAB® XC8 C Compiler Version 2.32 Release Notes for PIC® MCU

MPLAB Ecosystem Downloads Archive | Microchip Technology

 

"Dare to be naïve." - Buckminster Fuller

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

awneil wrote:

HKPhysicist wrote:
That is the craziest experience that I face with C.  When I was a boy, I could do this in Day One with BASIC

Go on. Using printf is day-one stuff in C.

 

Yes, I have given up the hope to programme conversion from float number to character strings in C.

 

I will pay a little more money to buy 2K more RAM and use sprintf comfortably.  enlightenedsmiley

 

Solved.  yes

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HKPhysicist wrote:
buy 2K more RAM (sic?) and use sprintf comfortably

Flash?

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HKPhysicist wrote:
I will pay a little more money to buy 2K more RAM and use sprintf comfortably. 
It's not RAM that's the problem - it's the flash that you need to hold the sprintf() code (especially if it is the sprintf() with float support code). I thought we'd already established that even though you are posting on an AVR micro site that you are actually using a 128K PIC. So how could the size of sprintf() be an issue? Even the most ill formed implementation of sprintf+float is unlikely to occupy more than 4K max. or have you really already used 127K or your 128K ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I remember being a bit shocked going from BASIC and ForTran (both of which essentially used floats by default) in my first computer experiences, to using mostly integers in my first "serious" computer science class.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

HKPhysicist wrote:
I am testing this on a PIC18F47Q10 with 128KB.  It has a built-in temperature sensor.  Cool! yes

Well apart from the 31 level hardware stack (which itself may make for a difficult floating point implementation), that looks a perfectly modern chip with decent peripherals and competitively priced.

 

@HKPhysicist WTF: Was this entire thread about saving a few K of that 128K, or did you just want to avoid reading the Microchip C18 or XC8 documentation ?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am further gobsmacked.   69 messages so far.    Absolutely no reason to worry about Flash usage or execution speed. 

 

The OP could have just read the first 3 or 4 replies.   Job done.

 

Incidentally,   full-fat printf() with an LCD was too big for an ATtiny4313 in Codevision.

Most dtostrf() source codes on the Internet are fiddly.

 

In practice,  you use dtostrf() for human display.   float precision is only about 7 digits but most humans don't want this much.

I wrote a fairly simple dtostrf_kbv() that handles "999.9999" to "9999999" with formatting, rounding, sign and will run on a tiny2313 using f-p.

 

Yes,  you can do everything in integers.    Floating point is easier to manage.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to be clear:

 

OP is not using an AVR

OP is not using an AVR C compiler

 

Local knowledge about AVR specific things (such as the fact that AVR-LibC for avr-gcc has a non-standard dtostrf() function) are of no relevance to what (s)he is doing.

 

OP is using a 128K PIC, it will not have any problems accommodating the obvious (and standard) printf().

 

I have questioned previously why HKPhysicist is posting PIC questions on an AVR support site. One day I guess we may get an answer?

 

Applying AVR knowledge to PIC problems simply does not work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
void lcd_writeString(uint8_t* string, uint8_t row) {
    lcd_setAddr(row, 0);
    uint8_t i = 0;
    for (i = 0; i < 16; i++){
        if (string[i]) {lcd_writeChar(string[i]);}
        }
    lcd_returnHome();
}

 

 

Hello Guys,

Now, I know how to run the sprintf().

 

Then, I want to improve the above official routine provided by Microchip.  Since it does not contain bug, I have to do it myself.

 

Since you say the "for loop" does not stop even if string[i] == '\0' , could you suggest a nice method?  blush

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
if (i = 0; (string[i] <> '\0') && (i < 16); i++){...

 

Do you think whether the compiler will pass this?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is what you have-

 

void lcd_writeString(uint8_t* string, uint8_t row) {
    lcd_setAddr(row, 0); //line 'row', column 0
    uint8_t i = 0;
    for (i = 0; i < 16; i++) {
        if (string[i]) {
            lcd_writeChar(string[i]);
        }
    }
    lcd_returnHome();
}

 

Will assume row 0 is line1, and row 1 is line 2. First, there should be a check if row is valid, if not just return. Second, you know you will start at column 0 and you have 16 chars until the end of the row. Quit using the string when you hit the end, or until the count gets to 16. Now why not clear those unused chars to a space char so you do not see chars from the last time.

 

void lcd_writeString(uint8_t* string, uint8_t row) {

    if( row > 1 ) return;
    lcd_setAddr(row, 0); //line 'row', column 0
    uint8_t i = 0;

    //until no more lcd space, or no more string
    while( (i++ < 16) && *string )
 lcd_writeChar( *string++  );

    //until no more lcd space, write space char

    while( i++ < 16 ) lcd_writeChar(' ');
    lcd_returnHome();
}

 

You also mention using sprintf, go lookup what snprintf is, you want that version. If you use snprintf, you will not be overrunning a buffer as you will be telling it how much buffer you have, and in your case it sounds like you will use 16+1, where the +1 allows for the 0 terminator.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HKPhysicist wrote:

if (i = 0; (string[i] <> '\0') && (i < 16); i++){...

 

Do you think whether the compiler will pass this?

 

This makes no sense whatsoever. So no, the compiler will not pass this.

 

HKPhysicist wrote:
Since you say the "for loop" does not stop even if string[i] == '\0' , could you suggest a nice method?

 

If you want to stop at `\0` just do a `break` when you encounter a `\0`.

 

But the original code, obviously, is not intended to stop at `\0`. It simply supposed to skip `\0` characters.

Dessine-moi un mouton

Last Edited: Sat. Jun 12, 2021 - 09:41 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AndreyT wrote:

But the original code, obviously, is not intended to stop at `\0`. It simply supposed to skip `\0` characters.

But what if you wish to display the first custom character ? Then you'd need the 0x00 to go out to the LCD.

 

I believe the intention of the original coder was to implement an LCD line buffer where the 16 characters are written out to the respective line. There would be a buffer for each line. This technique has a few benefits:

 

It can simplify things where there is a need to read characters back from the LCD.

It may be a bit quicker because there is no need to clear a line first before writing stuff out.

It can prevent some flicker if you're using the ClearDisplay command before updating.

 

How stuff (esp. strings) get into that buffer is another matter.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>But what if you wish to display the first custom character ? Then you'd need the 0x00 to go out to the LCD.

 

You would use the custom char address values of 8-15 to avoid the use of 0, as the lcd masks off bit3 of the first 16 addresses.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

Will assume row 0 is line1, and row 1 is line 2. First, there should be a check if row is valid, if not just return. Second, you know you will start at column 0 and you have 16 chars until the end of the row. Quit using the string when you hit the end, or until the count gets to 16. Now why not clear those unused chars to a space char so you do not see chars from the last time.

 

void lcd_writeString(uint8_t* string, uint8_t row) {

    if( row > 1 ) return;
    lcd_setAddr(row, 0); //line 'row', column 0
    uint8_t i = 0;

    //until no more lcd space, or no more string
    while( (i++ < 16) && *string )
 lcd_writeChar( *string++  );

    //until no more lcd space, write space char

    while( i++ < 16 ) lcd_writeChar(' ');
    lcd_returnHome();
}

 

You also mention using sprintf, go lookup what snprintf is, you want that version. If you use snprintf, you will not be overrunning a buffer as you will be telling it how much buffer you have, and in your case it sounds like you will use 16+1, where the +1 allows for the 0 terminator.

 

 

This one sounds good.  Let me try.  Thanks.  smiley

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

N.Winterbottom wrote:

AndreyT wrote:

But the original code, obviously, is not intended to stop at `\0`. It simply supposed to skip `\0` characters.

But what if you wish to display the first custom character ? Then you'd need the 0x00 to go out to the LCD.

 

I believe the intention of the original coder was to implement an LCD line buffer where the 16 characters are written out to the respective line. There would be a buffer for each line. This technique has a few benefits:

 

It can simplify things where there is a need to read characters back from the LCD.

It may be a bit quicker because there is no need to clear a line first before writing stuff out.

It can prevent some flicker if you're using the ClearDisplay command before updating.

 

How stuff (esp. strings) get into that buffer is another matter.

 

 

Nice point.  Yes, if we need to read LCD content, the original Microchip codes are sensible.  yes

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

HKPhysicist wrote:

Nice point.  Yes, if we need to read LCD content, the original Microchip codes are sensible.  yes

 

Are you saying that the code you posted in #20 was written by Microchip ?

 

I would expect something more conventional from an established company like Microchip.

 

I am sure that there will be a regular LCD library for PIC18 that is supported by Microchip or by the established PIC18 community.

In much the same way as Atmel published LCD code for AVR.

 

But an LCD library does not need to be tied to any particular hardware chip family.

You simply have a set of functions e.g.

extern void lcd_init(uint8_t dispAttr);


/**
 @brief    Clear display and set cursor to home position
 @return   none
*/
extern void lcd_clrscr(void);


/**
 @brief    Set cursor to home position
 @return   none
*/
extern void lcd_home(void);


/**
 @brief    Set cursor to specified position
 
 @param    x horizontal position\n (0: left most position)
 @param    y vertical position\n   (0: first line)
 @return   none
*/
extern void lcd_gotoxy(uint8_t x, uint8_t y);


/**
 @brief    Display character at current cursor position
 @param    c character to be displayed                                       
 @return   none
*/
extern void lcd_putc(char c);


/**
 @brief    Display string without auto linefeed
 @param    s string to be displayed                                        
 @return   none
*/
extern void lcd_puts(const char *s);

You would use the same set of functions on 8051, PIC16, PIC18, PIC32, AVR, ARM, ...

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>Are you saying that the code you posted in #20 was written by Microchip ?

 

#16 has the info. MCC is generating the code.

 

 

 

There is a flaw in my #74 code (I happened to take a look at it again, and it jumped out at me). The i increment should have been after the *string test (the && order is reversed).

 

Something also a little more clear like the following would have been better. The i is dealt with in only one place, and the ternary takes care of either printing the char and incrementing string or a space is output. The ternary still requires a little thought, but is probably more clear than adding more if's into the mix.

void lcd_writeStringGood(uint8_t* string, uint8_t row) {
    if( row > 1 ) return;
    lcd_setAddr(row, 0); //line 'row', column 0
    uint8_t i = 0;
    while( i++ < 16 ) lcd_writeChar( *string ? *string++ : ' '  ); 
    lcd_returnHome();
}

 

These kind of things can be tested without hardware, and is a good idea to run through some tests on the pc, which can also be done online-

https://godbolt.org/z/sY8KjTfEK

I should have run the code through this test, as it would have shown the problem right away. The problem would show up when that last lcd column goes from used to unused as column 15 was not being cleared.

 

A typical day in any kind of programming- create problems and fix them.

Last Edited: Mon. Jun 14, 2021 - 12:38 PM

Pages