Printing floats using integer prints only - Page 1
Forum section: electronics / general

author: pyrohaz
author_rank: Regular Contributor
timestamp: July 15, 2014, 07:17:02 pm
content: Hi again!I'm trying to write a print float function without using sprintf (space consumption) which uses a standardised number printing function to transmit characters over USART. So far, I'm splitting the float into the integer and fractional part, printing the '-' character if negative, followed by the integer part of the number. I'm then printing the decimal point '.', and then the fractional part. To shift the fractional part up into the integer number, I'm multiplying it by the amount of precision I want. For example, to print 1.2345 to 2dp, my workflow would be (pseudocode):FloatNum = 1.2345IntPart = (int) FloatNum;FloatFracPart = FloatNum-IntPartIntFracPart = (int)(FloatFracPart*100)Print(IntPart)Print('.')Print(IntFracPart)Attempting this in code doesn't seem to be as successful for me however as when the number reaches 2.3, instead of going to 2.4, it seems to go to 2.39. (I'm testing this printing floats from 1-5 in steps of 0.1f), my code is:Num is sent to the function and is a floatCode:int32_t IPart, FracI; //Init variablesfloat Frac;uint8_t XPos = 0, Sign;if(Num<0.0f){Sign = 1;Num = -Num;}else Sign = 0;IPart = Num;if(Sign) XPos = PNum(-IPart);else XPos = PNum(IPart);if(Prec>0){Frac = Num - IPart;switch(Prec){case 1:FracI = (int32_t)(Frac*10.0);break;case 2:FracI = (int32_t)(Frac*100.0);break;case 3:FracI = (int32_t)(Frac*1000.0);break;case 4:FracI = (int32_t)(Frac*10000.0);break;case 5:FracI = (int32_t)(Frac*100000.0);break;}XPos = PChar('.');XPos = PNum(FracI);}Any help on this will be greatly appreciated!

author: bwat
author_rank: Frequent Contributor
timestamp: July 15, 2014, 07:27:05 pm
content: Floating point I/O wasn't really a solved problem for most programmers until the early 1990s. Before that it was a bit of a black art and many got it wrong. See "How to Print Floating-Point Numbers Accurately" by Guy L. Steele Jr. and Jon L White ACM SIGPLAN 1990. In the same ACM SIGPLAN you've got "How to Read Floating-Point Numbers Accurately" by William D. CLinger.I spent a whole summer getting it right for my Prolog compiler. I learned an AWFUL lot that I didn't even realise needed learning.

author: jeremy
author_rank: Super Contributor
timestamp: July 15, 2014, 07:28:00 pm
content: Try using sprintf to print the float and see if it is what you expect (just for testing). Floats cannot represent all numbers and you may be tripping on this.

author: legacy
author_rank: Super Contributor
timestamp: July 15, 2014, 07:31:07 pm
content: my advice is: you'd better think about thebinary formatused by the compiler and by the sw (soft float) or by the hw (real fpu) to handle a float (32 bit) or a double (64bit). In this way you have to think about mantissa and other stuff in order to translate them into something printable.

author: Dave Turner
author_rank: Frequent Contributor
timestamp: July 15, 2014, 07:55:52 pm
content: off the top of my head - add before your codemultiply by 10^precisionif positive add 0.5else subtract 0.5take integer partdivide by 10^precisionnow your code

author: suicidaleggroll
author_rank: Super Contributor
timestamp: July 15, 2014, 08:13:26 pm
content: If it were me, I would just multiply by 10^precision, round it, and then print, inserting a decimal when there are "precision" characters left. Basically the same as what Dave Turner suggested, but without the division at the end, since that just converts it back to a float and brings roundoff back into the problem.So if your float number was 1.2345 and you wanted 2 decimal places, you would multiply by 100 (123.45), add .5 (123.95), convert it to an integer (123), and then print it. First the 1, then the decimal, then the 23.I do this all the time when printing fixed precision values to an LCD from mcus. I even have an lcd_printf routine in my library that does all of this internally, you just pass it the int (which is actually a fixed precision fraction) and the precision, and it does the rest. The only difference in this instance is that you have a float, so you can convert it to fixed by multiplying by 10^precision and rounding.

author: Dave Turner
author_rank: Frequent Contributor
timestamp: July 15, 2014, 08:55:23 pm
content: suicidaleggroll You're right - I did say just off the top of my head.

author: larsdenmark
author_rank: Regular Contributor
timestamp: July 16, 2014, 07:52:46 am
content: Quote from: pyrohaz on July 15, 2014, 07:17:02 pmWhy do you want to fiddle with this? If you are lacking space on your MCU and wish to transfer some data to your computer then I would suggest transferring the data in binary form to the computer and do the conversion there (but you have to write you own program for reading and displaying the data).

author: ve7xen
author_rank: Super Contributor
timestamp: July 16, 2014, 06:51:45 pm
content: int(float) is always going to truncate after the decimal point, it does not round. Because of this, and because floating-point types can't represent all decimal values (e.g. 2.4 may be represented as 2.39999997 or such), sometimes you'll round down the floating-point error and get the wrong output, because you're calling int(399.99) which results in 399.As Dave Turner outlines, you need to round before you truncate; a cheap way to do that is to add/subtract 0.5 which will then cause the truncation to have the correct integer part as if it was rounded.In an embedded context it is often easier and cheaper on CPU/code space to use fixed point; have you considered this?

author: legacy
author_rank: Super Contributor
timestamp: July 16, 2014, 07:07:34 pm
content: Quote from: ve7xen on July 16, 2014, 06:51:45 pmi agree with this!the only advice i can add is: unfortunately the ALU is not saturated about add/sub & mulso this solution needs a bit extra care about the soft-fixedpoint done by ALU inside the CPU

author: nctnico
author_rank: Super Contributor
timestamp: July 16, 2014, 07:37:42 pm
content: In many cases you won't want fixed point to saturate. If you are using floating point now I suggest to use 32 bit integer.

author: pyrohaz
author_rank: Regular Contributor
timestamp: July 20, 2014, 05:48:57 pm
content: Hi All!Quote from: Dave Turner on July 15, 2014, 07:55:52 pmI tried this method but unfortunately didnt manage to get it to work, I got even worse truncation! Though I assume @suicidaleggroll sorted that out. Talking of which, I tried this method but couldn't think of a good way to count how many number characters in I was to add the precision. it seemed plausible for what I managed though (I figured it out for one integer part)Quote from: larsdenmark on July 16, 2014, 07:52:46 amI agree, that would probably be easier but I'm using putty and an RS232 adapter, I suppose I could've broken out a simple VB program to convert to floating point but I thought that it could possibly be easier to do on an MCU.I'm using an STM32F0 and adding in the sprintf library (stdio.h) consumes around 20k which is a bit annoying but is what I've resorted to!Quote from: ve7xen on July 16, 2014, 06:51:45 pmI'm soon moving up to the STM32F4 which includes a dedicated FPU, which I'm hoping to utilize in the near future, good idea though!In the end, I think I'm going to just stick to sprintf. I'll continue to fiddle around with making a floating point print function but its not a big deal as of yet I suppose.Thanks to all for the help however!

author: Dave Turner
author_rank: Frequent Contributor
timestamp: July 20, 2014, 10:09:42 pm
content: In my 'off the top of my head' suggestion I possibly didn't make it clear that by 10^precision I meant x10 for 1 decimal point, x100 for 2 decimal points etc.

author: IanB
author_rank: Super Contributor
timestamp: July 20, 2014, 10:30:05 pm
content: Quote from: pyrohaz on July 15, 2014, 07:17:02 pmAs mentioned above, make sure you do proper rounding with int(). For positive numbers, try this:FloatNum = 2.39999999IntPart = (int) FloatNum;FloatFracPart = FloatNum-IntPartIntFracPart = (int)(FloatFracPart*100+ 0.5)Print(IntPart)Print('.')Print(IntFracPart)For negative numbers, print the minus sign, make the number positive, then proceed as before.

author: miguelvp
author_rank: Super Contributor
timestamp: July 21, 2014, 12:02:59 am
content: Quote from: IanB on July 20, 2014, 10:30:05 pmThat will work.If the OP needs even more space to avoid floating point operations all together there are some expressions that will invoke floating point libraries.IntPart = (int) FloatNum;will need to process the floating point number to figure out the integer part.FloatFracPart = FloatNum-IntPart;will need to do floating point subtraction.IntFracPart = (int)(FloatFracPart*100 + 0.5);Other than changing 100 to 100.0f and 0.5 to 0.5f it will need to do a floating point multiplication an addition and a float to int conversion.All those can be avoided if working with the floating point directly.the mantissa has a 1 implied and the exponent is 127 biased (meaning you have to subtract 127 from the exponent to get the right power of 2 exponent.So in the picture above is a positive number0|01111100|01000000000000000000000sign 0 (positive)exponent 124 (- 127) = -3mantissa (1.)01000000000000000000000 since trailing 0 don't count this is 1.01 in binary. or 1.25 in decimal (1 plus one quarter)The value will be 1.01 * 2^(-3) or shift the dot left 3 places.or:0.00101so it's 2^-3 (1/8) + 2^-5(1/32) = 0.15625So what does this mean? you could use the floating point directly with some shifting and BCD encoding and avoid floating point functions all together, once you have the BCD digits you just need to add ASCII '0' (48 decimal) to get the character to be printed without further conversions.The STM32F0 can do multiplies so multiplying the fractional part of the mantissa by 10 won't need shifting, but if the MCU didn't have native multiplication, you can multiply by 10 by shifting the value to the left by 3 bits (times 8 ) and adding it to the value shifted to the left by one bit (times 2).It probably will take less than one hour of my time to code and explain this in more detail, so if the OP is interested (or anyone else), I'll do it.Maybe overkill for the STM32F0 but some other smaller MCUs can benefit from avoiding bringing in floating point functions in memory space.

author: suicidaleggroll
author_rank: Super Contributor
timestamp: July 21, 2014, 03:08:26 pm
content: Quote from: pyrohaz on July 20, 2014, 05:48:57 pmOnce you've converted to fixed precision it's pretty easy. My code uses a combination of %10 and /10 to pull off each digit, and tosses them into a byte array in reverse order. Then you just loop backwards through the array and print them off as (char) ('0' + bytearr). When there are "precision" array elements left, you print a decimal, and then keep going.