Large numbers / polynomial maths

Hello,

I am looking for my Arduino (uno or mega-not yet ran on mega) to perform a 3rd order polynomial calculation. Im not doing anything too complicated other than taking a A-D reading and obtaining a control output.

I am using float for all input parameters as follows (note "x3" etc relates as x to the power of 3 etc)

float x3 = 15.62500; // 3rd order value from excel Array LINEST calc
float x2 = -2343.81608; // 2nd order value from excel Array LINEST calc
float x = 117196.20227; // 1st order value from excel Array LINEST calc
float c = -1953394.88388; // constant value from excel Array LINEST calc

These values give me a solution for "y" given an input of "x".

On the whole its working well and behaving as i would expect. However, when i provide the same input value of X on consecutive runs i get slightly different results of Y.

This is likely due to the inherent error of float calculations.

During the calc, i believe i stay within the ranges, the values during calculation range close too 6,000,000 and to just close to -6,000,000

Now, i have revised the number of decimal places (not yet ran on arduino) down which keeps the output within the region of what i want, my hope was fewer decimal places may lead to less error?

float x3 = 15.625;
float x2 = -2343.816;
float x = 117196.202;
float c = -1953395.08;

To keep numbers sensible, ive already scaled the analogue input using map to a range of 48.0000-52.0000 which is the X value for my input.

My analogues are correct and the mapping is correct so im sure the error is float maths.

I've done alot in excel to try to remove all decimal places (eg x 1000 on everything) but this screws up the polynomial maths. Or, i end up with very large numbers or very small ones. This is where i found the scale range of 48-52 worked well for me.

The application for reference is to take pressure (800-1000mBar) and convert to a control signal for a blower. Using op-amps, ive scaled 0-1024 as 798.6 to 1003.4 mBar. I then map 0-1024 to 48.0000-52.0000 (because this corresponds to the mV output of the sensor and all the maths is sensibly manageable).

Perhaps i can use some numbers as float and others as double?
Is there an inbuilt polynomial function ive not seen?

Processing speed is not an issue, in fact i do a millis() comparison at the end over the start of the loop and its generally 0-1ms which is fine for me.

Any advise on handling large numbers with better maths accuracy would be good.

Thanks,
Stuart

To keep numbers sensible, ive already scaled the analogue input using map to a range of 48.0000-52.0000 which is the X value for my input.

So, you've thrown away 95+% of the range, to get values within limits, and wonder why the code you didn't show us produces inaccurate results, for some measure of accuracy. Did I miss something?

Doubles are actually floats on the arduino. It only has single precision. Since you only have 1023 states, why not pre-calculate and put it in a table in Flash.

And map is an integer function. Did you create your own for floats?

Is there ever a reason to use map()?

Yes, map works nicely for stuff like:

 analogWrite (pwm_pin, map (analogRead (A0), 0, 1023, 0, 255))

(OK that particular example can be done with a shift, but in general map is clearer and higher level)

I think that I see one problem right off.

float x3 = 15.62500; // 3rd order value from excel Array LINEST calc
float x2 = -2343.81608; // 2nd order value from excel Array LINEST calc
float x = 117196.20227; // 1st order value from excel Array LINEST calc
float c = -1953394.88388; // constant value from excel Array LINEST calc

Arduino float and double are both 32-bit IEEE Floating Point Piles Of Sand good to 6-7 significant digits.

This is likely due to the inherent error of float calculations.

Hence "piles of sand" that every time you move one, you get a little dirt and lose a little sand.

Add to that that most if not all Arduinos have no FPU.

64-bit type long long integers are good to 19 places. If you use fixed-point math or small units, you can keep precision and do the operations faster.

You might get a great speed if you can table your function for many values of x and interpolate as needed. That's a trick from way back, I have an example that returns sine values (times 10 to the however many decimal places I wanted, IIRC 4 to fit in ints) in usecs. You store the table in flash and only need RAM to read and interpolate, and did I mention it's FAST as in how many do you want per millisecond?

KeithRB:
Doubles are actually floats on the arduino. It only has single precision. Since you only have 1023 states, why not pre-calculate and put it in a table in Flash.

Beat me to it.

odometer:
Is there ever a reason to use map()?

It's for people who never got algebra.

Which is

GoForSmoke:
It's for people who never got algebra.

Which is a lot of artists. 8^)

odometer:
Is there ever a reason to use map()?

I never use it. I'm never sure exactly what it will do, but I'm pretty sure I won't like it. :slight_smile:

Ah yes:

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

It uses long, which I don't necessarily need, it does a multiplication and a division.

And the conversion is often totally unnecessary, like turning an analog read into millivolts to use in an if-else or switch-case.

GoForSmoke:

odometer:
Is there ever a reason to use map()?

It's for people who never got algebra.

Or arithmetic.
Or basic numeracy skills.

stuartsjg:
Hello,

I am looking for my Arduino (uno or mega-not yet ran on mega) to perform a 3rd order polynomial calculation. Im not doing anything too complicated other than taking a A-D reading and obtaining a control output.

I am using float for all input parameters as follows (note "x3" etc relates as x to the power of 3 etc)

float x3 = 15.62500; // 3rd order value from excel Array LINEST calc
float x2 = -2343.81608; // 2nd order value from excel Array LINEST calc
float x = 117196.20227; // 1st order value from excel Array LINEST calc
float c = -1953394.88388; // constant value from excel Array LINEST calc

These values give me a solution for "y" given an input of "x".

On the whole its working well and behaving as i would expect. However, when i provide the same input value of X on consecutive runs i get slightly different results of Y.

This is likely due to the inherent error of float calculations.

During the calc, i believe i stay within the ranges, the values during calculation range close too 6,000,000 and to just close to -6,000,000

Now, i have revised the number of decimal places (not yet ran on arduino) down which keeps the output within the region of what i want, my hope was fewer decimal places may lead to less error?

float x3 = 15.625;
float x2 = -2343.816;
float x = 117196.202;
float c = -1953395.08;

To keep numbers sensible, ive already scaled the analogue input using map to a range of 48.0000-52.0000 which is the X value for my input.

My analogues are correct and the mapping is correct so im sure the error is float maths.

I've done alot in excel to try to remove all decimal places (eg x 1000 on everything) but this screws up the polynomial maths. Or, i end up with very large numbers or very small ones. This is where i found the scale range of 48-52 worked well for me.

The application for reference is to take pressure (800-1000mBar) and convert to a control signal for a blower. Using op-amps, ive scaled 0-1024 as 798.6 to 1003.4 mBar. I then map 0-1024 to 48.0000-52.0000 (because this corresponds to the mV output of the sensor and all the maths is sensibly manageable).

Perhaps i can use some numbers as float and others as double?
Is there an inbuilt polynomial function ive not seen?

Processing speed is not an issue, in fact i do a millis() comparison at the end over the start of the loop and its generally 0-1ms which is fine for me.

Any advise on handling large numbers with better maths accuracy would be good.

Thanks,
Stuart

You have not made it clear what you are trying to do.

What is "y"? What kind of value do you expect "y" to be?

By the way, I was playing around with your polynomial on an old electronic calculator. I don't like your polynomial: it is prone to catastrophic cancellation.

KeithRB:
Since you only have 1023 states, why not pre-calculate and put it in a table in Flash.

This is an excellent suggestion.

odometer:
It's for people who never got algebra.

Or arithmetic.
Or basic numeracy skills.

Those damned bundles of pencils. When's recess?

Firstly many thanks for the vast number of detailed and very helpful replies - there are too many to quote in detail so ill try and reply/comment to the average of the comments.

Of greatest interest is the table idea - ive not tried this but it would be very easy to produce in excel with my equation applied and use excel to format for pasting into the code.

I shall not clutter this post asking how to do it as ill need to read up and try a few things first to get to grips with it.

As always, i try to work on the theory "nothing is impossible", combined with "somebody must have asked this question before" and the answers are generally always there!

The "loss of significance" is new to me as a term but i fully get what it means and i beleive this is whats affecting the repeatibility.

This was why i mapped the 0-1024 to a smaller/narrower range of numbers to try to get the coefficients and their sub-results within a calculable range without being too big or too small.

I pull everything out via serial and if, in excel, i flow through from 0-1024 to my mapped range and apply the formula then i get the correct and repeatable results so i know everything except the main calculation is correct.

The guts of the code which does the calc looks like this:

 if (SetMLL < mVMeasured < SetMHH)
  {
    BiasCurve = (x3 * mVMeasured * mVMeasured * mVMeasured) + (x2 * mVMeasured * mVMeasured) + (x * mVMeasured) + c;
  }

before and after this i have statements to govern the output when the result is below the "LL" [=LowLow] point or above the "HH" [=HighHigh] point.

I hear all that's said about avoiding unnecessary conversions and i would normally just try to work with the ADC values but they produced coefficients which were crazy.

Ill try the lookup table and report back how i get on.

Speed is not an issue, i need perhaps a 1-2sec response time so even ms is fine.

Thanks again to all.

 if (SetMLL < mVMeasured < SetMHH)

If true (SetMLL is less than mVMeasured) or false (SetMLL is greater than or equal to mVMeasured) is less than SetMHH, do something. That's not really what your code looks like, is it?

Why even use a polynomial in the first place?

And what is your idea of a "calculable range"?

Hello,

Just an update - i have used the lookup table method and its working perfectly. i get the result required/expected every time. The program is also more reliable and has been running for a few days without hanging.

I think i do have some of my "if" statements wrong as PaulS has alluded to but im working on these things since the guts of it is working fine.

Odometer, the polynomial had been formed in excel after experimental results were looked at. I had thought a neat solution was to have the arduino just calculate each time however the table idea suggested by others was much more practical.

By calculable range i was meaning using alternative numbers caused me to have numbers which resulted in grossly inaccurate ranges. Take the x^3 parameter of 15.625. A small change in range of input data made this become a tiny number (looking back it was 0.0000000012647552) which the float maths gave inaccurate results from. Another change pushed it to a huge number (probably around 15,625,000,000 ) in the calculation which i think was too big. All the prescaling was to get the numbers into a "Goldilocks" range which worked reasonably well. What has been spot on is to let Excel & many Xeon cores to do the hard work and let the Arduino work within its comfort zone!

After logging for a few days i am seeing "out of range" spikes which i need to filter out but ill post separately if i cant find a solution on that as its very different to the subject of this post.

Thanks once again for the help.