Representing small values in C - Page 1
Forum section: electronics / general

author: yashrk
author_rank: Frequent Contributor
timestamp: July 24, 2016, 04:26:39 pm
content: Hello Guys,I have to represent and use small values like 1.40715e-09 in a calculation of a polynomial equation. I know I can use maths.h but I guess that will be more processor intensive, is there any better way of doing it ?I have to solve this using dynamic values of x in each loop -K = 1.40715e-09 * x^5 + 9.18189e-08 * x^4I am using PIC18F4620 in MPLAB X.Thanx.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 24, 2016, 04:29:03 pm
content: If x and K are bounded integer ranges, it should suffice to use fixed point.Otherwise, a floating point polynomial doesn't sound like the kind of thing you should need to compute on a PIC... find another solution?Tim

author: dannyf
author_rank: Super Contributor
timestamp: July 24, 2016, 04:43:20 pm
content: Code:K = 1.40715e-09 * x^5 + 9.18189e-08 * x^4rewrite it to:Code:K = a * (b * x + 1) * (c*x)^2^2Find the appropriate a, b and c.

author: JPortici
author_rank: Super Contributor
timestamp: July 24, 2016, 09:44:04 pm
content: if you have enough space/not so many X values use a lookup table for the output otherwise you WILL have to use floating point math to keep a parvency of precision so unless you get a MCU with an FPU you will have to let it take its sweet time to calculate the value. a pic18 is much better than a pic16 in this case. a dspic is much better than a pic18 in this case. a pic32MZ EF is much better and so onbut i don't know your requiremenets, if the value has to be calculated on the fly all the timeor it is a sporadic measure etc

author: westfw
author_rank: Super Contributor
timestamp: July 25, 2016, 12:52:33 am
content: QuoteWhat is the range of "x"? What is the expected range of the result? What is the required accuracy of the result?If you have floating point involved anyway, then using "small" values as you've shown should not cause any additional problems (though it is probably desirable to avoid floating point entirely, if you can.) (although, with REALLY small numbers (Planck's constant squared, my memory says - a very educational experience, that assignment...), you can exceed the limits of some floating point formats...)

author: Mechatrommer
author_rank: Super Contributor
timestamp: July 25, 2016, 12:59:21 am
content: without giving much clue of how many bit resolution required, the answer can be anything. for eg, is x integer? whats the min max value of x?

author: yashrk
author_rank: Frequent Contributor
timestamp: July 25, 2016, 01:46:15 am
content: X is an 22 bit ADC value which is always positive

author: yashrk
author_rank: Frequent Contributor
timestamp: July 25, 2016, 01:52:46 am
content: And yes the value has to be calculated at least ones in every time it goes through the "main" loop, so I do want it as fast as possible and for the accuracy I need it to be accurate till 2-3 decimal places

author: Mechatrommer
author_rank: Super Contributor
timestamp: July 25, 2016, 02:56:16 am
content: thats complicated, you'll need to track exponent from -9 to 24, probably need to device for mantissa storage/tracking as well. you'll probably will end up as slow as fp op. bed time here others may help, good luck.

author: MarkF
author_rank: Super Contributor
timestamp: July 25, 2016, 03:05:36 am
content: How fast do you need to sample the DAC and what else does the PIC need to do?If you have lots of time, just use doubles and do the calculation. Normal software development is to code it the most straight forward way and then optimize it if you need to reduce memory size or decrease the computation time.

author: nctnico
author_rank: Super Contributor
timestamp: July 25, 2016, 07:36:13 am
content: Quote from: MarkF on July 25, 2016, 03:05:36 amVery true! I assume there is a soft floating point library for PIC as well so start using that and see if it is fast enough. Also using a faster processor (ARM) may be a lot easier than trying to squeeze performance out of a slow 8 bit processor.

author: dmills
author_rank: Super Contributor
timestamp: July 25, 2016, 03:07:18 pm
content: Small PIC floating point libs are sometimes very much NOT IEEE754, so be sure to read the documentation......Regards, Dan.

author: Kalvin
author_rank: Super Contributor
timestamp: July 25, 2016, 03:54:20 pm
content: Depending of the precision required, performing the arithmetics using logarithms and performing the need conversions using lookup-tables may also be an option. Googling using the following magic keywords may give some ideas: floating point arithmetic using logarithms

author: MosherIV
author_rank: Super Contributor
timestamp: July 25, 2016, 04:07:02 pm
content: Assuming the number of fractional bits stay the same between calculations, have you looked at how DSPs handle floating point numbers?https://en.wikipedia.org/wiki/Q_%28number_format%29

author: hamster_nz
author_rank: Super Contributor
timestamp: July 25, 2016, 08:59:51 pm
content: Are you calculating K given an 'x' value, or , or solving for x, when K is known?Anyhow... this might be betterCode:28428252164 * K = (4*x^5 + 261*x^4)

author: helius
author_rank: Super Contributor
timestamp: July 25, 2016, 09:13:45 pm
content: Are we really talking about the C language here? I see a whole bunch of code snippets that use ^ for exponentiation. Really?

author: MarkF
author_rank: Super Contributor
timestamp: July 25, 2016, 09:46:43 pm
content: Quote from: helius on July 25, 2016, 09:13:45 pmYa. That's the notation in C. Get use to it.

author: Len
author_rank: Frequent Contributor
timestamp: July 25, 2016, 10:07:30 pm
content: Quote from: MarkF on July 25, 2016, 09:46:43 pmNo it isn't. ^ is the binary xor operator in C.yashrk wasn't writing C code. He wrote a mathematical formula and asked how it should be calculated in C.

author: dannyf
author_rank: Super Contributor
timestamp: July 25, 2016, 10:16:53 pm
content: "Anyhow... this might be better"Using x4 to calculate x5.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 25, 2016, 10:40:04 pm
content: If he the OP really wants to calculate this efficiently on a low end part the best way would be to normalize everything into a custom floating point format (i.e. a 32 bit number representing 0.5000 (0x80000000 to 0xFFFFFFFF) and an exponent (maybe from 2^-32768 to 2^32768).Write a routine to normalize the numbers...Code:void normalize(u32 *mantissa, u16 *exponent) {if(*mantissa == 0) {*exponent = 0;return;}while(*mantissa & 0xC0000000 != 0) {*mantissa<<=1;*exponent--;}Then multiplications becomeCode:void multiply(u32 *mantissa_1, u16 *exponent_1, u32 *mantissa_1, u16 *exponent_1) {*mantissa_1 = (u32)(((u64)(*mantissa_1) * (u64)*mantissa_2)>>32);*exponent_1 = *exponent_1 + *exponent_2;normalize(&mantissa_1, &exponent_1);}Addition is a bit trickier, as you have to put things to a common exponent (whichever is the highest) and then add, and allow for an overflow.With that infrastructure the calculation becomes:Code:void calc(m_x, e_x) {*m_temp1 = *m_x;*e_temp1 = *e_x; /* temp_1 = x */multiply(&m_temp1, &e_temp1, &m_x,& e_x); /* temp_1 = x^2 */multiply(&m_temp1, &e_temp1, &m_x,& e_x); /* temp_1 = x^3 */multiply(&m_temp1, &e_temp1, &m_x,& e_x); /* temp_1 = x^4 */*m_temp2 = *m_temp1; /* temp2 = x^4 */*e_temp2 = *e_temp1;multiply(&m_temp1, &e_temp1, &m_x,& e_x); /* temp_1 = x^5 */multiply(&m_temp1, &e_temp1, &m_const1, e_const1); /* temp1 = const1 * x^5 */multiply(&m_temp2, &e_temp2, &m_const2, e_const2); /* temp1 = const2 * x^4*/add((&m_temp1, &e_temp1, &m_temp2, &e_temp2); /* temp1 = const1 * x^5 + const2 * x^4 *//* K is now in m_temp1 & e_temp1 */}

author: dannyf
author_rank: Super Contributor
timestamp: July 25, 2016, 11:55:12 pm
content: This is easy:produce a complete compilable routine that converts a 22-bit adc to a floating point output, as per the OP's formula, with the fastest execution and/or smallest space.Go!

author: MarkF
author_rank: Super Contributor
timestamp: July 26, 2016, 12:35:20 am
content: Quote from: Len on July 25, 2016, 10:07:30 pmYou're right. Don't know what I was thinking.

author: MarkF
author_rank: Super Contributor
timestamp: July 26, 2016, 12:39:05 am
content: Quote from: hamster_nz on July 25, 2016, 10:40:04 pmTest... Test... Test...Many times the compiler will out smart you and it will actually take longer to execute with the optimization.I've seen it very often.

author: westfw
author_rank: Super Contributor
timestamp: July 26, 2016, 12:41:21 am
content: QuoteSigh. A 22bit raw ADC-reading number like "1000000", or an analog voltage value derived from that like "((1000000 * Vref)/4194304.0)" ?Thermocouple? (link to datasheet?) Expected temperature range or analog voltage range? (22bit ADC, eh? Damn!)but don't most of those high-res ADC have conversion times in the ms to 200ms range? Are you sure you need "fast" ?start with some simple optimization like:Code:x4 = x*x*x*x;K = x4*(1.40715e-9 *x + 9.18189e-8);and see if it is "fast enough" (premature optimization is the root of much evil.)(normally, I'd hope that a compiler would produce equivalent code from a straightforward expression like "K = 1.4e-9*pow(x,4) + 9.2e-8*pow(x,5)", but ... I'm not sure, I especially don't trust the 8bit PIC compilers, and x*x*x*x is probably faster than pow(x,4) anyway.)(edited: replace embarrassing misuse of "exp" function with correct "pow" function.)

author: helius
author_rank: Super Contributor
timestamp: July 26, 2016, 02:00:00 am
content: Quote from: westfw on July 26, 2016, 12:41:21 amexp(x) computes \$e^x\$. pow(x,y) is defined to return a double, anyway.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 26, 2016, 02:49:47 am
content: The range of numbers here is the problem - (2^22)^5 goes from 0 to 1.33e+33. And as one term is the 4th power, and the other is the fifth power, for most of values of 'x' the x^5 term dominates and the other term is insignificantly tiny once x > 70,000 or so (when X is a range of 0 to 4,000,000 or so).Quote from: westfw on July 26, 2016, 12:41:21 amThe full quote is..."We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%" - Donald KnuthOn an small PIC this calculation could well be the critical 3%.I don't think that the original post was optimizing too soon - they were trying to explore their options early enough that they would be able to weight up options.

author: Mechatrommer
author_rank: Super Contributor
timestamp: July 26, 2016, 08:23:41 am
content: Quote from: westfw on July 26, 2016, 12:41:21 amCode:y=x*xz=y*yis probably faster

author: Kalvin
author_rank: Super Contributor
timestamp: July 26, 2016, 09:09:06 am
content: If you perform numerical analysis for the typical numbers x and calculate the values, it might be possible that even a simple, piece-wise linear look-up table will give sufficient accuracy. Taking the logarithm of the number x and computing the lookup table for the logarithms would possibly reduce the lookup table size considerably without introducing too much of an error.Edit: Better yet, you can normalize the x values using simple shifts for some convenient numerical range, compute the lookup table for the normalized values, use the normalized values for indexing the lookup table, and finally denormalize the the lookup values using simple shifts again. Should get pretty accurate results without too much of computational efforts.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 26, 2016, 11:33:33 am
content: So I was a little bored, and wanted to see if custom floating point code would actually work. I tested it on laptop CPU, and not indicative of performance on a PIC, but it at least proves that the math is somewhat sound....PerformanceCalculating approx 1,000,000,000 times (250 times through the 2^22 range)Hardware Floating point = 5 seconds (200M calcs per second)My floating point = 60 seconds (16M calcs per second)Approximately 12 times slower than H/W floating point. Not as bad as I would have guessed, without even optimizing...If I used 64-bit multiplications, (rather than 4x32-bit ones) performance is then only 7x slower than H/W floating point.Error vs floating pointDifference between h/w floating point and my floating point:Max error at 2097333 is 0.000000004570%Accuracy could be relaxed to improve performance.Code:/*********************************************** Testing a range limited custom floating point* for calculating c1*pow(x,5)+c2*pow(x,4) over* a range of x=0 to x=2^22-1.* Assumes 32-bit 'signed' and 'unsigned' types.********************************************/#include <stdio.h>#include <time.h>double c1 = 1.40715e-09;double c2 = 9.18189e-08;/* 1.4071499998200009962090462067863e-9 in custom floating point*/unsigned m_c1 = 3244666990;signed e_c1 = -29;/* 9.18189e-08 in custom floating point */unsigned m_c2 = 3308124510;signed e_c2 = -23;/****************************************************************//* Conversion for printing *//****************************************************************/static double fixed_to_float(unsigned m, int e) {double t;t = m;t /= 65536;t /= 65536;while(e > 0) {t *= 2; e--;}while(e < 0) {t /= 2; e++;}return t;}/****************************************************************//* Adjust so MSB of mantissa is set *//****************************************************************/static void fixed_normalize(unsigned *m, signed *e) {/* Adjust to have a one in the MSB of mantissa */if(*m == 0) {*e = 0;return;}while((*m & 0x80000000) == 0) {*m <<= 1;*e = *e -1;}}/****************************************************************//* Multiply two numbers together, using only 32bit math *//****************************************************************/static void fixed_mult(unsigned *m1, signed *e1, unsigned *m2, signed *e2) {unsigned h1,l1,h2,l2;unsigned h1h2, h1l2, l1h2, l1l2;unsigned carry,acc;if(*m1 == 0 || *m2 == 0) {*m1 = 0;*e1 = 0;}h1 = *m1 >> 16;l1 = *m1 & 0xFFFF;h2 = *m2 >> 16;l2 = *m2 & 0xFFFF;h1h2 = h1 * h2;h1l2 = h1 * l2;l1h2 = l1 * h2;l1l2 = l1 * l2;carry = 0;acc = l1l2 >> 16;acc += h1l2;if(acc < h1l2) carry += 0x10000;acc += l1h2;if(acc < l1h2) carry += 0x10000;acc >>= 16;acc += carry + h1h2;*m1 = acc;*e1 += *e2;fixed_normalize(m1,e1);}/****************************************************************//* Add two numbers together *//****************************************************************/void fixed_add(unsigned *m1, signed *e1, unsigned *m2, signed *e2) {if(*m2 == 0) {return; /* Nothing to add */}if(*m1 == 0) {*m1 = *m2;*e1 = *e2;return; /* Nothing in m1 at the moment */}/* Do we need to move the decimal on m1*2^e1 ? */while(*e2 > *e1) {*m1 >>= 1;*e1 = *e1 + 1;}/* Do we need to move the decimal on m2*2^e2 ? */while(*e1 > *e2) {*m2 >>= 1;*e2 = *e2 + 1;}/* As e2 = e1 we can now just add */*m1 += *m2;/* Test for overflow */if(*m1 < *m2) {*m1 = (*m1>>1) | 0x80000000;*e1 = *e1 + 1;}}/****************************************************************//* The main calculation in custom fixed point *//****************************************************************/double calcFixed(unsigned i, unsigned *m, signed *e) {unsigned m_x = i;signed e_x = 32;unsigned m_t1;signed e_t1;unsigned m_t2;signed e_t2;fixed_normalize(&m_x, &e_x);m_t2 = m_t1 = m_x;e_t2 = e_t1 = e_x;fixed_mult(&m_t1, &e_t1, &m_x, &e_x); /* t1 = x^2 */fixed_mult(&m_t1, &e_t1, &m_x, &e_x); /* t1 = x^3 */fixed_mult(&m_t1, &e_t1, &m_x, &e_x); /* t1 = x^4 */fixed_mult(&m_t2, &e_t2, &m_t1, &e_t1); /* t2 = x^5 */fixed_mult(&m_t1, &e_t1, &m_c2, &e_c2); /* t1 = c2 * x ^ 4 */fixed_mult(&m_t2, &e_t2, &m_c1, &e_c1); /* t2 = c1 * x ^ 5 */fixed_add(&m_t1, &e_t1, &m_t2, &e_t2);*m = m_t1;*e = e_t1;return 0;}/****************************************************************//* The same calculation in floating point *//****************************************************************/double calcFloat(unsigned i) {double xd = i;return c1 * xd*xd*xd*xd*xd + c2* xd*xd*xd*xd;}/****************************************************************//* Test harness to verify the code *//****************************************************************/int main(int argc, char *argv[]) {int start_time;int end_time;int j;unsigned i;double t = 0;double max_error=0.0, min_error=0.0;int max_i = 0, min_i = 0;/* Time the H/W floating point routine */start_time = time(NULL);for(j = 0; j < 250; j++) {for(i = 0; i < 0x400000; i++) {t+= calcFloat(i);}}end_time = time(NULL);printf("Float took %i seconds\n", end_time-start_time);printf("t is %f (to prevent optimization)\n",t);/* Time the custom point routine */start_time = time(NULL);for(j = 0; j < 250; j++) {for(i = 0; i < 0x400000; i++) {unsigned m;signed e;calcFixed(i, &m, &e);}}end_time = time(NULL);printf("Fixed Took %i seconds\n", end_time-start_time);/* Check the accuracy */for(i = 1;i < 4*1024*1024; i++) {unsigned m;signed e;double error;calcFixed(i, &m, &e);error = 1.0 - fixed_to_float(m,e)/calcFloat(i);if(max_error < error) {max_error = error;max_i = i;}if(min_error > error) {min_error = error;min_i = i;}}printf("Max error at %i is %3.6f%%\n",max_i, max_error);printf("Min error at %i is %3.6f%%\n",min_i, min_error);return 0;}

author: dannyf
author_rank: Super Contributor
timestamp: July 26, 2016, 11:54:49 am
content: "If you perform numerical analysis"You would quickly come to the realization that for moderately large x, the x5 term dominates, ,,,,

author: Kalvin
author_rank: Super Contributor
timestamp: July 26, 2016, 12:03:36 pm
content: Quote from: dannyf on July 26, 2016, 11:54:49 amOnly if abs(x) > 1.0. If abs(x) < 1.0, the abs(x^5) term will be less than abs(x^4).

author: dannyf
author_rank: Super Contributor
timestamp: July 26, 2016, 12:03:41 pm
content: The number to beat is 3000 instructions per calculation.

author: dannyf
author_rank: Super Contributor
timestamp: July 26, 2016, 12:39:09 pm
content: And about 600 bytes in code size.

author: Howardlong
author_rank: Super Contributor
timestamp: July 26, 2016, 01:10:01 pm
content: I may have missed it, and I apologise if I have, but what performance is the OP looking for?While we can certainly do many tricks once we understand the inputs and outputs, if, say, 1 or 2 ms is good enough with the compiler's software floating point then it becomes little more than an academic exercise. While I love solving such exercises in performance analysis, without a requirement goal it's a bit meaningless.I wrote a complete autonomous antenna satellite tracking system about 12 years ago based on a PIC18, it did all the calculations for real time presiction and point antennas for multiple satellites for a given set of orbital parameters (Kelerian elements). That was pretty scary, but it worked pretty well and was remarkably fast, tens of updates/sec, and would predict upcoming AOS and LOS (when each satellite is above the horizon) on the fly too, an even more computationally intensive thing to achieve.

author: dannyf
author_rank: Super Contributor
timestamp: July 27, 2016, 12:02:12 am
content: QuoteIf you allow approximation, I can reduce that number to 2000 instructions for 99.98% of the x's values. Flash size no material change.