Floating Point Calculations to Fixed Point?

So I've been doing a project in which floating point calculations have slowed it down to the point where a critical timing function is thrown off considerably, and the output I get is about half of what it should be. Quick searches of the internet have informed me that floats are incredibly inefficient in Arduino Unos, so I should somehow convert everything to fixed point math.

I quickly wrote this test code to see the impact of the change from fixed point to floating point. I ran this code once using all floating point numbers and having the readVoltage() function return a float, and it took about 13.5 seconds.

I then converted as much of it to fixed points as I could figure out (which is the code I've pasted here), and it takes 12.5 seconds to run. I understand that if done properly, fixed point math should be 40x faster than floating point math, and fixed points can do everything floating points can.

How can I improve this code so it runs faster?

//An example of a program that takes an analog input, converts it to voltage, and displays
//Speed testing features have been added. Will output the time the code took in ms at the end


const int resistorFactor = 5115; //divide by 10 to get actual factor, 511.5

unsigned long time;

void setup() {
  Serial.begin(9600);

}


void loop() {
  unsigned long sTime = millis();
  int i = 0;

  while(i < 1000) { //Run the ReadVoltage() funtion a bunch, and output 1) what it equals and 2) How you would convert it to human readable numbers
    Serial.println(ReadVoltage());
    Serial.print(" ");
    Serial.println(ReadVoltage() / 100.0);
    i++;
  }
  unsigned long eTime = millis();
  Serial.println((eTime - sTime));
  Serial.end();


}

int ReadVoltage() { 
  int val = analogRead(A5);
  val = 709; //This is just a testing variable, to trick it into thinking its actually connected to a battery
  float fvolt = ((val / (resistorFactor / 10.0)) * 5);
  return fvolt * 100;

}
  float fvolt = ((val / (resistorFactor / 10.0)) * 5);

Looks a lot like floating point math to me. It doesn't matter what the function return type is.

Well, that's my question. I don't know how to make that line fixed point without losing precision in the final output.

Well, that's my question. I don't know how to make that line fixed point without losing precision in the final output.

The whole idea behind fixed point math speeding things up is that you deal with the values as ints. For instance, instead of 1 second being 1/60th of a minute, 1 second is a unit itself. You deal with milliseconds, seconds, minutes, and hours as a number of milliseconds, not as a number of seconds. That is 1 millisecond is one millisecond, not 0.001 seconds. One minute is 60000 milliseconds, not 1/60th of an hour.

Without knowing what you doing with the value you compute, it's hard to tell you how to make computing it faster.

PS: Multiplication is faster than division. If you can rearrange terms to multiply instead of divide, the result will be computer much more quickly.

Sorry for not providing enough information again.

The code is intended to convert a number from the analog input range into a human viewable number representing voltage. In reality this code would be loaded onto an Arduino connected to, say, a 9v battery using a voltage divider. The voltage divider allows the arduino to detect voltage values over the 5.0V limit of the analog input by scaling down the incoming voltage using resistors. Then, the analog input is scaled based on a factor determined by what resistors are used in the voltage divider.

In this case, the resistorValue should be 511.5. It doesn't have any units, and it is derived from dividing the maximum analog input range (1023) by two.

In this particular bit of test code, "val" represents the analog input. Since it's not actually connected to a battery at the moment, I wrote: val = 709, which is an arbitrary number that I picked. For val = 709, I want the code to print "6.93" volts.

I've taken the above code and removed the confusing bits, and commented it out better.

//An example of a program that takes an analog input, converts it to voltage, and displays it.
//For testing purposes, I have told the program that it is getting "709" as its analog input. I want it to output "6.93"


const int resistorFactor = 5115; //divide by 10 to get actual factor, 511.5

void setup() {
  Serial.begin(9600);
}

void loop() {

  
    Serial.println(ReadVoltage()); //This will print "693" (mV)
    Serial.print(" ");
    Serial.println(ReadVoltage() / 100.0); //This will print "6.93" (v)
    Serial.end();
}

int ReadVoltage() { 
  val = 709; //This line represents the analong input of "709" being taken from a voltage splitter connected to a 9v battery.
  float fvolt = ((val / (resistorFactor / 10.0)) * 5); //This line scales the analog input, and converts it to voltage.
  return fvolt * 100; //My failed attempt is using "int"s rather than floats. Instead of outputting "6.93", the function outputs "693"

}

Why is the exact voltage relevant? Wouldn't 89% charged be good enough? Maybe you have valid reasons; I'm just asking - the more we know, the better we can help.

The project itself isn't really what I care about in this case; I just isolated this bit of code from a much larger (working) project in order to mess around with trying to make it work with fixed point math so I can learn the concepts behind making the switch. The project itself works fine, but is starting to run into problems because of ridiculous computation times (sometimes as much as 100 ms) during critical timing functions.

I'm not trying to make it work here: the code outputs the correct number as it is. I'm just trying to convert it into something faster, so I can then apply the concept to another couple pages of badly done floating point code. I just learned arduino from the internet a couple days ago, so I didn't know any better.

The whole project is basically a multimeter, which records and graphs voltage and current from a battery, with the final objective of recording the total capacity (in mAh) of said battery.
I wanted it to output everything to two decimal places of precision (6.54 volts, .25 A, etc) because that is what a commercial multimeter does.

If you want to convert a floating point value into a fixed point value then you need to decide what your fixed point scale will be, multiple the float value by that scale, and convert it to an integer of the appropriate type to hold that value. Usually the fixed point scale would be determined by the resolution that you need for your data, rounded up to a convenient number.

As has been pointed out, you need to decide how much precision you want in your answer, and scale your values accordingly.

For example, in your code:

float fvolt = ((val / (resistorFactor / 10.0)) * 5); //This line scales the analog input, and converts it to voltage.
  return fvolt * 100;

You are scaling by 100, so 6.93 V is returned as 693. (as you indicated in your comment)

To do this computation with integer arithmetic, you would need to consider how large an integer variable you will need for the result, and the intermediate results and how they are scaled. You have already indicated that resistorFactor is scaled by 10 (the value 5115 represents 511.5). If your output domain is to be 0-12 volts, say, you would need to represent 0-1200 as a value, so an int, which holds up to 32767 is fine.

The calculation

volt = ((val / (resistorFactor / 10.0)) * 5);

must be scaled by 100, and no decimal places are permitted, so that gives us

volt = 100 * ((val / (resistorFactor / 10.0)) * 5);

Integer divides are a danger, because unlike float, you can't recover the fractional part. for example ( 5.0 / 10.0 ) * 100.0 = 50.0 (floating), but ( 5 / 10 ) * 100 = 0 (integer). Lets look at the math of
val / ( resistorFactor  / 10.0 ) )
this is equivalent to
( 10.0 * val / resistorFactor )
which is a much nicer calculation for integers. Replacing the original subexpression with the new one gives us:

volt = 100 * ( (10 * val / resistorFactor ) * 5 );

further simplification yields

volt = 5000 * val / resistorFactor ;

The first intermediate result during evaluation will be 5000 * val. Since val can range up to 1023, the largest possible result is 5115000, which is too large for int, but OK for long, so you should either declare val and resistorFactor as long to get the compiler to do the math using long, or coerce them in the expression to get the same effect.

The second stage of evaluation will compute 5115000 / 5115, giving 1000 (which represents 10.00 V) as the result for the largest possible input. The smallest is 0, and the math becomes trivial.

When doing scaled calculations using integers, you have to guard against two things: loss of precision due to truncation from divisions, and loss of precision due to overflowing your integer. One possible solution to the example here would be:

int volt = 5000 * (long)val / (long)resistorFactor ;

Note that doing the multiplications first puts us in a better position when we then do the division, as long as the number to be divided doesn't get so big that it overflows the variable. You do have to do the analysis for these situations, and add parentheses to force the order of evaluation that you want, if necessary.

Also remember, if you have two numbers, each scaled by 100 and multiply them, then you need to divide the answer by 100 to make the scaling correct (ex 5 scaled by 100 is 500 and 3 scaled by 100 is 300; multiplying 500 by 300 gives 150000, which is scaled by 10000, so to have it scaled by 100, you need to divide 150000 by 100 to get 1500).

You must be careful when doing addition and subtraction that the numbers you are combining are scaled by the same factor. For example, 3.2 scaled by 10 is 32 and 4.06 scaled by 100 is 406; if you add them, you get 438, which is wrong! If they are both scaled by 100, you get 320 + 406 = 726, the equivalent of 7.26 (notice that the one scaled by the smaller factor had to be scaled up).

Division works like multiplication, except that you lose the scaling factor of the denominator. So 6 scaled by 100 is 600 and 3 scaled by 10 is 30. If you divide 600 by 30 you get 20, which is 2 scaled by 10. You might have to adjust the scaling on that to be consistent with the desired result or with the next subexpression you are going to combine it with.

All in all, you have to be quite careful and check things thoroughly.

I hope that helps!

Thank you very much! That was EXACTLY what I was looking for!

int volt = 5000 * (long)val / (long)resistorFactor ;

as resistorFactor is a const 5115 you can optimize it even further.

int volt = 1000L * val / 1023;

if you allow a small error you could say 1023-> 1025 resulting in:

int volt = 40 * val / 41; // all the math fit in 16 bit int - error is ~0.2%

or use one float multiplication with optional rounding
int volt = round(val * 0,9775171);

my 2 cents