Doing percentage calculation without floats?

Hello,

I was wondering if it's possible to do a calculation with percentage without using a float. The math is done in the interrupt routine and I would like to keep that as short and fast as possible.

This the calculation i would like to make:

PPM_BASE[PPM_cur_ch] = PPM_BASE[PPM_cur_ch] * 1.10;

But i want to replace the 1.40 by an integer number to speed up the calculation process PPM_BASE has an value of 1000 - 2000 with a resolution of 1024 steps (ad convertor). And for the calculation the 1.4 value can be between 0.00 and 1.10 with 2 decimals at most.

Also the 1.4 value should become of an value outside the interrupt routine and this function will be called every 1ms. So I would like to place and edit the integer value outside the interrupt routine. But the changes of the PPM_BASE value should be inside the interrupt routine since this is the actual signal.

It's for an 16 channel PPM transmitter program.

Division using floats is slow. Multiplication is not (as slow). Do you have a real problem, or a perceived problem?

at the moment it isn't causing any trouble yet, but the program is far for complete and will be much longer later on. So that's why I try to keep down the time and what I know is that using floats in maths calculation is slowed down by pretty much. Calculation with integers is must faster. Since this calculation is made in my interrupt routine it's wise to get it as fast as possible.

You can do something like this: num * 11/10. if PPM base is 2000 it should be fine.

what I know is that using floats in maths calculation is slowed down by pretty much.

That is true for division. It is not necessarily true for multiplication.

Hello Paul,

The fact is that my value outside the interrupt is already between 0 and 140 (signal time one side is 500, to reach 2200 it needs to be * 1.4) So i have to convert the int, to an float. And that means I have to work with a float all from the beginning also have to devide some parts in the code.

I rather like to use another way so i can keep using my integer value of 0 - 140. Since converting an integer value of 140 to 1.40 also means working with floats in that part.

Or is there away around that fact? It seems simply saying PPM_HIGH_LIMT[0] = PPM_HIGH_LIMIT_BASE[0] / 100;

gives errors saying the PPM_HIGH_LIMIT_BASE was not declared. Even if it is in the code like all the other int's.

signal time one side is 500, to reach 2200 it needs to be * 1.4

According to my calculator, 500 * 1.4 is not 2200. Maybe your calculator needs new batteries.

So i have to convert the int, to an float. And that means I have to work with a float all from the beginning also have to devide some parts in the code.

What int needs to be converted to a float? Why does the value to be scaled need to be a float? The scale factor does, but not the to-be-scaled value or the scaled value.

It seems simply saying PPM_HIGH_LIMT[0] = PPM_HIGH_LIMIT_BASE[0] / 100;

gives errors saying the PPM_HIGH_LIMIT_BASE was not declared.

Where, in that snippet, is PPM_HIGH_LIMIT_BASE declared?

http://snippets-r-us.com is down the road a ways.

Even if it is in the code like all the other int's.

Obviously, that statement is not true.

Use fractions.

This

PPM_BASE[PPM_cur_ch] = PPM_BASE[PPM_cur_ch] * 1.10;

becomes this

PPM_BASE[PPM_cur_ch] = (PPM_BASE[PPM_cur_ch] * 110L) / 100L;

If you don’t want to divide by 100 using the division operator, then use this:

PPM_BASE[PPM_cur_ch] = fastdiv100(PPM_BASE[PPM_cur_ch] * 110L);

together with this function:

long fastdiv100 (long x) {
  // made by odometer
  // absolute value of argument not to exceed 52 million 
  x*=41L;
  x-=(x>>10);
  return ((x+(1L<<11))>>12); 
}
PPM_BASE[PPM_cur_ch] = (PPM_BASE[PPM_cur_ch] * 110L) / 100L;

can also be expressed as

PPM_BASE[PPM_cur_ch] = PPM_BASE[PPM_cur_ch] + PPM_BASE[PPM_cur_ch]  / 10;

replace multiplication by an addition, might be faster

Yes works only for factor 1.1 , other factors might be less trivial

factor 1.1 1.2 1.3 1.4 1.5

x = x + x / 10;
x = x + x / 5;  
x = x + ( x + x<<1) / 10;
x = x + (x << 1) / 5
x = x + x / 2;

robtillaart:

PPM_BASE[PPM_cur_ch] = (PPM_BASE[PPM_cur_ch] * 110L) / 100L;

can also be expressed as

PPM_BASE[PPM_cur_ch] = PPM_BASE[PPM_cur_ch] + PPM_BASE[PPM_cur_ch]  / 10;

replace multiplication by an addition, might be faster

Yes works only for factor 1.1 , other factors might be less trivial

factor 1.1 1.2 1.3 1.4 1.5

x = x + x / 10;

x = x + x / 5;  
x = x + ( x + x<<1) / 10;
x = x + (x << 1) / 5
x = x + x / 2;

Or more straightforwardly (the compiler will likely optimize most of these the same as your methods):

x = x + 1*x / 10;
x = x + 2*x / 10;  
x = x + 3*x / 10;
x = x + 4*x / 10;
x = x + 5*x / 10;

Floating-point division is actually faster than integer division in many cases. Unless there is a real problem, I would suggest you stick with the code that works. Saving the programmer's time understanding what's happening is actually more important than saving the Arduino's execution time.

MorganS: Floating-point division is actually faster than integer division in many cases.

I highly doubt that is the case with floating-point operations done entirely in software. Floating-point division is essentially implemented as a 24-bit integer division, plus extra overhead to handle the exponents and to unpack and repack the floating-point number itself.

Well this is how i've fixed it eventually.

PPM_BASE[PPM_cur_ch] = PPM_BASE[PPM_cur_ch] * PPM_HIGH_LIMIT[PPM_cur_ch];

and in the loop:
PPM_HIGH_LIMIT[i] = PPM_HIGH_LIMIT_BASE[i] * 0.01;

It seems that the float isn't affecting the speed too much at this moment. PPM_HIGH_LIMIT_BASE is an integer with the value of 0 - 140. (actual PPM calc. signal is 500 that's why I need the 1.40 to get up to 2200. It's an servo based signal with the center being 0 going left beeing -500 and going right +500)

next function to go to is the EXP function for the PPM. Let's see how we can fix that one.

veenie: Hello,

I was wondering if it's possible to do a calculation with percentage without using a float.

Yes by working with smaller units and printing larger ones. Like working in mm to do fractional meters.

f1 = f1 * 1.10; f1 is float. 6.099 µs.

l1 = l1 / 10; l1 is long. 37.665 µs.

s1 = s1 / 10; s1 is short. 13.771 µs.

Tested on an Uno R3. Times adjusted for one load and one store.

Heed @MorganS's advice. And, don't "optimize" unless you know where the problem actually lies.

Optimization discussion continued here... http://forum.arduino.cc/index.php?topic=336020.0