2
\$\begingroup\$

Here are the specs for the chip/chip family that our vendor is using.

For our product we are asking them to perform a calculation by multiplying a number by a factor that has a decimal point...for example: 1.07, 0.93, etc.

We are then asking them to round to the nearest integer.

However, the number that they calculate sometimes have a rounding error. Ex: On my calcuator 56 * 2.36 = 132.16...rounded to 132. However, the microcontroller will return an answer of 133 displayed on an LCD of our product.

My question is, how can this be fixed? I've been asking the vendor many times and they can't seem to get it right.

Is calculating floating point number really hard for microcontrollers that don't have that native function? (Related question: DIY FP - Implementing floating point math on a microcontroller without a hardware FPU)

UPDATE The range for the results are 1 to 780.

\$\endgroup\$
  • 1
    \$\begingroup\$ Is there any chance that they are using the wrong rounding function, and rounding to the top instead of the nearest integer? \$\endgroup\$ – clabacchio May 9 '12 at 7:18
  • 3
    \$\begingroup\$ This could easily be a bug in the application code and not in the implementation of fixed/floating point maths. \$\endgroup\$ – Cybergibbons May 9 '12 at 7:25
8
\$\begingroup\$

You don't say anything about the range of your numbers or the required precision. But you often don't need floating point for this, fixed point often will do. Just work with integers and place a virtual decimal point at a certain position. A 16-bit number could for instance have 8 bits left of the decimal point, and 8 bits after. This will give you a range from 0 to 255 (I'm assuming unsigned numbers), with a precision better than 0.01.

Rounding is then simple: just look at the most significant decimal, the bit directly right of the decimal point. If that's a zero, then the integer part should be left untouched. If it's a one, add 1 to the integer part.

edit
There's a zillion ways of rounding: round up/down, round to nearest, round to zero, banker's rounding,... Be sure you and the vendor speak the same language.

\$\endgroup\$
3
\$\begingroup\$

I guess it's just a matter of precision.

Suppose the factor 2.36 is represented by a fixed point number with only 4 bits to the right of decimal point (fractional bits), you get:

56 * 2.36 in fixed point representation with only 4 fractional bits:
(56 * round(2.36 * 2^4)) / 2^4 = 
(56 * round(37.76)) / 16 =
(56 * 38) / 16 = 133

The solution would be just to use more fractional bits (maybe 8).

56 * 2.36 in fixed point representation with 8 fractional bits:
(56 * round(2.36 * 2^8)) / 2^8 = 
(56 * round(604.16)) / 2^8 =
(56 * 604) / 256 = 132

But still then it is possible for some factors to get the same effect, only less likely/frequent.

It only works always perfectly if the fixed point representation of the factor doesn't loose any bits by rounding.

This is the case if (and only if):

  • the fractional part of the factor is a multiple of 1/2^n and
  • the fixed point representation of the factor uses at least n fractional bits.
\$\endgroup\$
3
\$\begingroup\$

First you need to define rounding between you and the vendor, they may have properly done it based on their definition.

Second if you are only talking about two decimal places there is no reason to use floating point, you should have spec'ed it without floating point, and maybe you can change it to be defined to not use floating point. 8 bit micro or not, not needed can easily do 8 or 16 bit math and sort out all the rounding you desire for a few decimal places (a competent programmer never needs floating point hardware because he should always be able to keep the point in his head, credited to John Von Neumann).

It doesnt matter if it is a microcontroller or a cray, floating point formats have problems, as do compilers as do programmers. Just like 1/3rd and 2/3rds are a problem in a decimal world, 1/10th for example is a problem in binary based floating point programs. If I want to add 2/3rds to 0.83 is that 0.667+0.83 = 1.49... or 0.67+.83 = 1.50 one would round up the other wouldnt, both could be argued to be correct. Yes, true you still have to define your rounding when using fixed point (do you take 200/3 and get 66 (representing 0.66) or do you use rounding and take ((400/3)+1)/2) and get 67?) Your question is not doing divides but multiplies, you still end up with the same kinds of problems. Also note floating point in hardware in a processor is not something to get all excited about, much harder to work around the bugs that are likely there (the pentium was not the first nor the last fpu with problems, your operating system has to fix them or maybe they just leave them there for you to fail these days). A soft fpu is slower but much easier to fix after the chip is in production.

2.36 is like 1/3rd is to decimal it is a repeating digit thing when represented in binary float. this bit pattern appears fairly early. 101110000101000111 (easier to see in double than single, once seen in double you can then see it in the single). I cant make that or an abbreviated version of that cause enough error to push it to 133 though. Looking at single precision ieee, which isnt the only floating point format...

01000000000101110000101000111101 2.36
01000000000101110110110110110111 (132.5/56 = ~2.36607...)

the 110 repeats and is rounded on the backend. not that this is remotely related at this point, but just a curiosity, change it to

01000000000101110110110110110110

times 56 and you get 132.499...

The bottom line is that as you stated here when you use a pencil and paper or use a calculator and you multiply 56 * 2.36 and get 132.16 and that rounds to 133 you need to make that clear that you consider this a failure/bug they need to fix. Perhaps it doesnt matter how or why they came up with the wrong answer, so long as the next rev of the software gives the right answer. I dont know how hard it is to cause this product to perform this math, but you should test it for many different values to see if it always gets the expected answer, or even better if possible have the section of code that does the math extracted and validate it in isolation against a long list of operands. Or even specify it that way, the software must be able to get the expected result for this list of operands and answers.

\$\endgroup\$
0
\$\begingroup\$

An easy fix is to simply subtract a half from your microcontroller result.

From your example:

56 * 2.36 = 132.16 rounds to 133

then

(56 * 2.36) - 0.5 will round to 132

\$\endgroup\$
  • 1
    \$\begingroup\$ That's fine in the given example, but you can't apply that in general without knowing what rounding method was used. \$\endgroup\$ – Federico Russo May 9 '12 at 11:44
  • \$\begingroup\$ Well, it would appear to be rounding up so this would then be a valid fix \$\endgroup\$ – BullBoyShoes May 9 '12 at 11:53
  • 1
    \$\begingroup\$ Rounding up isn't something which just happens, you have to take deliberate action, most likely adding 0.5... \$\endgroup\$ – Federico Russo May 9 '12 at 12:02
  • 2
    \$\begingroup\$ I don't think the poster has control over the code. \$\endgroup\$ – Cybergibbons May 9 '12 at 15:29
  • \$\begingroup\$ @Cybergibbons - you're right, I don't have control over the code. \$\endgroup\$ – milesmeow May 16 '12 at 5:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.