What's new

(Warning VERY TECHNICAL) Ye Olde Printf (sigh)

Cyberman

Moderator
Moderator
I have an embedded project that is close to driving me batty.

Also not floating point here refers to single precision float only. double precision is not something you use on systems you have to scape for 1K of RAM (LOL).

So here is it's I need to print formatted floating point data (float to asc) without using C libraries.

Now I've actually done quite a bit of the work already there are just a few nasty problems. Unfortunately ACM documents aren't available at work (more on what that means in a bit).

First I split the float into it's integer and fractional components. For those who are floating point clueless this is not as easy as it sounds. (If someone wants the long explanation I can give one later).

Now the data needs to be printed with a fixed field width and precision (amazingly similar to the printf format codes "%#.#f" where the first # is the field width and the second # is the precision (IE decimal places). THIS is where things get very tricky.

This function CANNOT work like the typical sprintf luaded by many generations of clueless people. Why? It's not memory safe, which for what I am working on is quite an important feature.

So litterally I cannot print greater than fieldwidth characters no matter how convenient or easy it would be.

Now the issues with the numbers.
OK so I start with the integer part. To get the digits I use the following
Code:
if(int_component)
{
 while((int_component)&&
   (field_width))
 {
  store_to_buffer_dec(int_component % 10 + '0');
  int_component /= 10;
  field_width--;
 }
}
field width prevents from over running the buffer and also keeps track of how many digits have been pasted. I preadjust the buffer pointer so that it points to the end of the number (this is calculable). I also account for the minus symbol and a few other things. The problem comes between the fractional and integer parts (figures?) Let's say we have the number
1.3
Now that innocent looking number is ... not 1.3 in floating point no no it's 1.299999938 or something instead. So ... what happens is that if you do
Code:
printf("%5.1f", 1.3);
in regular C library you would get
1.3 however with my function you get 1.2... round off error (sigh).

But wait there is MORE let's talk annoying numbers for example 999999.9 you think "oh that's easy" well guess what it's EVIL. 999999.875 is the ACTUALY number in floating point AND if you do something like
Code:
printf("%10.3f", 999999.9);
in normal C code you get 999999.9 (correct) but I get 999999.8 (LOL).

These aren't easy to correct either. since parts of the data are fractions. I suppose I could do rounding (via addition.

Hmmm anyhow for those of you who want to aspire to computer science that is something I'm sure you will find .. annoying. References?

1996 quickly and accurately
1990 accurately <not full text you can't get it due to ACM requirements>

However I believe I have found a solution but it will still be a PAIN to do. (sigh)

Cyb
 

new_profile

New member

Exophase

Emulator Developer
Hey, you linked a paper from the department I graduated from, great :>

Of course, I think the algorithm it describes is a little more than what you're asking for, since you just want proper rounding to the digit specified. As you said, you can get around this with addition, by adding 5 before dividing by 10. You can also check the remainder when you stop printing to see if it's above a threshold that divides as you do.
 
OP
Cyberman

Cyberman

Moderator
Moderator
What I currently do is split the float into 2 integers.

int_component
frac_component
these are unsigned 32 bit integers.
Get the sign (negative) then work through the field.
First I check if precision and field with have extra space.

EX
field_width is decremented if the number is negative (the negative is always first).
field_width - precision gives a expected number of digits.
I then get int_digits from the float (the count of integer digits).
This is needed to be sure to know if I have 'extra' space'
I compare int_digits to the expected space if the number of digits is greater than that which is expected
so no I have N spaces
I move the buffer position to the location of just before the decimal point.
Now I begin moding int_component by 10 and then putting that into the buffer and decrementing the pointer. then int_component is divided by ten. This is done until int_component is 0.
if the number is negative we add the minus sign here
if we have digits left over we pad tell we reach the begining of the buffer (spaces).

if precision is greater than 0
we add a decimal place after where the integer part was stuffed.
Each time something gets added to the buffer the field_width is decremented. The minus sign is compensated for earlier is all.
Now we have our fractious fraction. while precision and field_width aren't zero we mask off the high digits and multiply by 10 the fraction. Then we get the left over digit and past that to the buffer (incrementing)until we run out of precision or field_width.

This obviously has flaws. What if the fraction over flows at the precision digit? Then I have to increment the integer based on the fraction overflow. (much swearing).

So basically this gets 99% of numbers correct. The ones that aren't OK are things like 1.3 0.95 (if the precision is one) we have overflow in the fraction. Not sure how to handle this really. The problem is I've already pasted the integer digits to the display now. I believe the real way to do this is like someone suggested add 0.5 0.05 0.005 etc at the precision digit to the fraction and then look for the fraction to overflow. If so mask the bits etc. Not sure if that will work. I'll have to test it. I did find a redacted method to 'fix' the numbers after they've been pasted. I just look for over flow at the last digits then iterate backwards to the first digits. It gets the error numbers down to 8 in 20000 It's still the wrong method.

Cyb
 
OP
Cyberman

Cyberman

Moderator
Moderator
Solution

The problem has been fixed, Prior to splitting the integer and fraction portions of the float I call a function that computes a bias number. The bias number is added or subtracted (depending if the number is positive or negative respectively). The bias number is computed by dividing 0.5 by 10, precision count times. After this step everything proceeds as normal. The floating point value basically has to be altered prior to doing anything else. +-INF 0 and +- NAN are all checked prior to adding/subtracting the bias value. This resolves rounding issues with no decimal places as well (0 for precision). IE
Code:
printf("%*.*f", 10, 0, 0.999999);
"         1"
which is correct, it would have shown as 0 before.

Cyb
 
OP
Cyberman

Cyberman

Moderator
Moderator
Is anyone interested in me posting the solution to this?

Anyone have interesting in knowing how to make this type of code?

It's obscure, weird, and most people seldom need it but it was definitely interesting to me. I guess most people program for machines with > 1K of ram here? (don't laugh that's what I wrote the code for).

I found out borland's printf statement isn't 100% correct by the way nor is Microsofts. The article went into some detail for correct number printing but I digress.

I also created code for making floating point half types from float but that's even more obscure to people. When you need to cram a floating point value into a 16bit word .. you don't have much choice. Worked great though!

Anyhow if anyone wants to know that kind of detail for floating point, I can write a short tutorial on it or something. Ah this reminds me of my university days of writing my own transcendental functions in C.

Cyb
 
Last edited:

Top