View previous topic :: View next topic |
Author |
Message |
WK Guest
|
printf, floats, 5/4 roundup |
Posted: Thu Nov 12, 2009 2:55 pm |
|
|
I would like to share some experience with using the CCS printf
function to print floats. Hope it is of use to CCS customers.
In a product we did in 1998 we used printf w format "%1.7e" to
send readable floats out as 232. We noticed that occasionally
due to propogation of lsb errors in printf it would print a float
whose mantissa magnitudes were slightly too small, never
slightly too large.
For example 10.0 float + 3.0 float (10.0 is 1.250*2^3,
3.0 is 1.500*2^1, 14.0 is 1.750*2^3, all are exactly
representable) : the actual float result in register was
8260000 hex which is exactly 14.0 but printf per above
would report +1.3999999E+01 rather than +1.4000000E+01.
This is no big deal because in any system with limited resolution
you have to expect propagation of errors in the lsb range in a
complex cascade of calculation and converting a 32 bit float
to decimal exp format is a complex operation. We were going
to add 5/4 roundup after the fact but ran out of room in the
PIC16C66.
Foward a few years. We bought the CCS compiler for the
PIC18F parts and noticed just how much code would fit in
a part like the 18F2620 and how much ram was available.
So we modified the function we were calling w printf to stack
characters in ram locations rather than xmit. Then we look
at the 8th significant figure of the mantissa and if it is 5 or
greater we do a cascaded round up 7th thru 1st sig fig
(be sure to skip the location were the decimal point lives).
Of course the "numbers" in the byte locations are now ascii
equivalents but since the 0 through 9 characters are continuous
and ascending "numeric equivalents" it is easy to add any carries
that occur. Because you are now dealing with integers there
will not be any unexpected errors in the procedure.
Once we are done with the round up we copy only the first 7 sig
figs to a new set of ram locations ie +1.3999999E+01 becomes
+1.4000000E+01 after round up and +1.400000E+01 after copy
to new array. Then finally we blast those locations out the
232 or spi in sequence.
So far no problems - testing continues.
Printf w "%1.7e" format in an 18F part takes up about 688
bytes of code and our round up routine (written in c) takes
up about another 490 bytes. Total xqt time seems to be about
960 us worst case at 40MHz.
Other methods we tried that did not include printf to output floats as
readable decimal exp format either used way more code space and/or
were a lot slower (those guys at CCS are GOOD). So the method of
using CCS printf followed by a round up routine seems like the best
trade-off: usable size, good speed, and "pretty" floats with 7 good
significant figures to make the final user happy.
Of course, all of the number crunching before the printf has to be
good to 7 figures also. If your processing prior to the printf is not
quite that good, just trim off one significant figure in the operations
above. |
|
|
Douglas Kennedy
Joined: 07 Sep 2003 Posts: 755 Location: Florida
|
|
Posted: Thu Nov 12, 2009 4:04 pm |
|
|
You are getting the numbers to look pretty which is what you want but they probably aren't 100% accurate.
The float in binary will be accurate to within the storage size of the float. The CCS conversion to base ten is accurate but often not pretty unless the value is a power series of 2. Making 0.999999999 look pretty as 1.0 has the disadvantage of adding a fudge factor ( a.k.a rounding) when the result could be absolutely accurate ( a power series in 2). Now if you sacrifice the 2 least significant digits the base ten representation will be reliable. There is no error it is just the binary notation and the decimal notation are different and any conversion shows the differences. Ex decimal 1/10 0.1 is never able to be represented in binary without some truncation. |
|
|
WK Guest
|
|
Posted: Thu Nov 12, 2009 11:41 pm |
|
|
Dear sir, please read my original post more closely. In the example I
give all of the numbers are EXACTLY representable, yet printf will not
give the desired result. We all know that 0.1 cannot be represented,
but if you read my post carefully you will see that is not the point at all.
The point is that occasionally printf will print a decimal exp format float
that is ever so slightly in error (in the smaller mantissa magnitude
direction) even when the input is exactly representable and some users
may want a work around for that.
I am responding here in public so that CCS users will not be scared off
after reading your response because the method described will be
quite usable for many users. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Fri Nov 13, 2009 1:07 am |
|
|
Quote: | In the example I give all of the numbers are EXACTLY representable | Yes, but this doesn't necessarily mean, that they are printed exactly, although it would be desirable.
Without looking into the details, I assume that this is an issue of the used binary-to-decimal implementation.
I also assume, that it can be done better, but I'm not aware of a C standard specification, that the
conversion must be done exactly. Please correct me, if I'm wrong. |
|
|
WK Guest
|
|
Posted: Fri Nov 13, 2009 2:27 am |
|
|
Thank you for the clarification, we do not know any of the specifications,
we are just electrical engineers who were forced to learn C just a little
if we wanted to use the CCS compiler to make our black boxes smarter.
For years we just wrote assembler but then saw some examples of how
code efficient the CCS compiler was and have never looked back. |
|
|
Douglas Kennedy
Joined: 07 Sep 2003 Posts: 755 Location: Florida
|
|
Posted: Fri Nov 13, 2009 7:31 am |
|
|
The situation in which certainty of CCS inaccuracy occurs is when the floating pt mantissa when raised by its exponent represents a power series of two. The example you give of a mantissa having a whole number meets this criteria ( any whole number is a power series of two) and you observe that CCS has the printf representation incorrect. I understood your solution as adding a fudge factor to fix this but I didn't hear you say it would only be applied in the case of a power series of two. If you only fudge a power series of two and only when CCS has it wrong then essentially you have just fixed a CCS printf bug. If the 5/4 is more expansive than just the power series and when CCS has it wrong it is a fudge factor to make things look prettier. |
|
|
WK Guest
|
|
Posted: Fri Nov 13, 2009 4:30 pm |
|
|
We don't add a fudge factor all the time, only when 8th sig fig is >=5.
You may be right that on numbers that are not exactly representable.
We introduce an upward error (never more than one in the 7th sig fig).
When printf does not give the exactly desirable result we have seen it
always falls short and then by anywhere from equiv of one to three lsb.
With a 23 bit mantissa, if the bin mantissa is close to one you have almost
8 sig fig (dec) and if it is close to two then a bit over 7 sig fig (dec).
So we may be trading off a tad of accuracy but would rather have that
than have the second or third significant figure be in "error" (i.e., if you
print +1.399999e+01 instead of +1.400000e+01 some users will say that
the second significant figure is in "error") and it does not matter if they
are technically correct or not if that is their perception.
The absolute best bet is to xmt the 32 bit Microchip float and convert it
elsewhere with double precision but if you are restricted to talking to a
dumb display or printer that is not an option. Thanks for all of the
feedback. |
|
|
WK Guest
|
|
Posted: Fri Nov 13, 2009 6:19 pm |
|
|
DK, we have never seen printf fall short with anything other than
x.9999996 thru x.9999999 mant and thought about doing as you say and
trying to detect only those very situations where printf did fall short but
decided against it because it felt dangerous.
Back in 98 we found a CCS float multiply bug and a float sq root bug
(which we reported and they corrected quickly) that only surfaced after
tens of thousands of runs/compares because they were so specific and
narrow (even when the test runs purposefully targeted the "usual suspect"
areas for arguments) that they normally did not show.
So we are nervous that if we did a specific "detect and fix" that was too
complicated then the detect or fix themselves might have a hard to detect
bug that we built into them, thus the relatively simple method we
described in the original post. If we were real programmers we would
probably be braver. |
|
|
|