int getTempo()
{
int tempo;
//User can enter tempo in bpm
tempo = (aserveGetControl(07) / 127) * 250;
//equation to convert tempo in bpm to an integer in ms to use with aserveSleep
return ((1000/tempo) * 60);
}
program won't run past this function, get the following error:
Thread 1:EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0)
If, as I suppose, aserveGetControl returns an integer between 0 and 127, tempo will always be zero (unless aserveGetControl returns exactly 127), since you are performing an integer division, which truncates the result to the integer part. You should invert the division and the multiplication in both your expressions, and be ready anyway to deal with the fact that aserveGetcontrol may return 0.
When you use integer maths, you should do the multiplication(s) before division(s).
Example 1 (truncation to zero)
(100 / 127) * 250
= (0) * 250
= 0
On the other hand
(100 * 256) / 127
= 25600 / 127
= 201
Example 2 (loss of precision)
(1000 / 27) * 60
= (370) * 60
= 2220
On the other hand
(1000 * 60) / 27
= (60000) / 127
= 2222
Try this:
int getTempo()
{
int tempo;
//User can enter tempo in bpm
tempo = (aserveGetControl(07) * 250) / 127;
//equation to convert tempo in bpm to an integer in ms to use with aserveSleep
return (60 * 1000) / tempo ;
}
Related
I am trying to create a conversion table using the C programing language. I want to convert the temperature from -250 °F to 250 °C increments of 10. However, I am not getting the Celsius output:
#include <p18f458.h>
#include <stdio.h>
#pragma config WDT = OFF
#define LOWER -250 /* lower limit of table */
#define UPPER 250 /* upper limit */
#define STEP 10 /* step size */
void main(void)
{
int fh, cel;
cel = (fh - 32) * 5 / 9;
for (fh = LOWER; fh <= UPPER; fh = fh + STEP)
printf("%d \t %6.1f\n", fh, cel);
while(1);
}
Fahrenheit Celsius
-250
-240
-230
-220
-210
-200
-190
-180
-170
-160
-150
-140
-130
-120
-110 .......
The code runs on a PIC18F458 microcontroller.
Recalculate each time
Use floating point math
// dddd123456789012ffffff
puts(" Fahrenheit Celsius");
// cel = (fh - 32) * 5 / 9;
for (fh = LOWER; fh <= UPPER; fh = fh + STEP) {
double cel = (fh - 32.0) * 5.0 / 9.0;
printf(" %4d %6.1f\n", fh, cel);
}
If wanting to avoid floating point types and math, use a scaled integer math to calculate decidegrees to achieve "%6.1f" like output. Scale temperature by 10. Integer division truncates the fraction, so add a signed offset of 5 to form a rounded result.
int offset = (fh - 32) > 0 : 5 : -5;
int deci_celcuis = ((fh - 32) * 10 * 5 + offset) / 9;
Printing is a little tricky. Various approaches exist.
int whole = deci_celcuis/10;
int frac = deci_celcuis%10;
char *sign = (whole < 0 || frac < 0) ? "-", "";
printf(" %4d %s%d.%d\n", fh, sign, abs(whole), abs(frac));
As others have noted: 1) use floating point to avoid integer division and truncation errors, 2) recalculate values inside the loop.
It would be a shame to miss this opportunity to produce parallel tables of F->C and also C->F for the given range.
#define LOWER -250
#define UPPER 250
#define STEP 10
int main() {
puts( " F C C F" );
for( int i = UPPER; i >= LOWER; i -= STEP ) {
printf( "%6.0f %6.0f", (double)i, (i - 32.0) * 5.0 / 9.0 );
printf( " " );
printf( "%6.0f %6.0f\n", (double)i, i * 9.0 / 5.0 + 32.0 );
}
return 0;
}
F C C F
250 121 250 482
240 116 240 464
230 110 230 446
220 104 220 428
210 99 210 410
200 93 200 392
// omitted...
-220 -140 -220 -364
-230 -146 -230 -382
-240 -151 -240 -400
-250 -157 -250 -418
Ordinary mercury thermometers condition people to expect warmer temperatures at the top, and cooler temperatures 'below'... This table reverses the sequence presented in the OP to conform to people's expectations.
cel = (fh - 32) * 5 / 9;
This statement is outside the loop, hence it will only ever print once.
. Add it in the loop and your code would work as expected.
My target is to compare two u_long timestamps, delivered by a GPS device. A long integer like 16290212 has the following structure:
hhmmssµµ
The following code snippet shows an approach how to parse a long integer to an integer array. But I think this is not very efficient. What would be the quickest way to compare two timestamps? I would love to use an UNIX timestamp, but it is not possible in this context.
u_long timestamp_old = 16290212;
u_long base = 1000000;
/* arr[0]: hours
* arr[1]: minutes
* arr[2]: seconds
* arr[3]: miliseconds */
int arr[4];
int i=0;
// parse timestamp_old
while(base >= 1)
{
arr[i++] = (timestamp_old / base);
timestamp_old = (timestamp_old % base);
base /= 100;
}
Your timestamps are u_longs; compare them the same way you compare any 2 u_longs, something like <.
What would be the quickest way to compare two timestamps?
I want to check if the difference is smaller than 400 ms
Perhaps not the fastest yet at least a starting point with a quick worst case. Note that ssµµµ is the same as µµµµµ.
int32_t GPS_to_ms(u_long timestamp) {
int32_t ms = timestamp%100000;
int32_t hhmm = timestamp / 100000;
ms += (hhmm%100)*60*1000;
int32_t hh = hhmm / 100;
ms += hh*60*60*1000;
return ms;
}
if (GPS_to_ms(timestamp_later) - GPS_to_ms(timestamp_first) < 400) {
// timestamps are in close succession.
}
To speed things up on average, 1) assume timestamp_later >= timestamp_first
is usually true 2) timestamps typically have the same hhmm
bool GPS_compare_400(u_long first, u_long later) {
int32_t ms1 = first%100000;
int32_t hhmm1 = first/100000;
int32_t ms2 = later%100000;
int32_t hhmm2 = later/100000;
if (hhmm1 == hhmm2) {
return ms2 - ms1 < 400;
}
return GPS_to_ms(timestamp_later) - GPS_to_ms(timestamp_first) < 400;
}
I assume the input is a string gpstime of the form "hhmmssµµµ". I assume that trailing zeros are always present such that there are always exactly three digits for the microseconds part.
1.
int h, m, s, us;
double t;
if(sscanf(gpstime, "%2d%2d%2d%3d", &h, &m, &s, &us) == 4)
t = (h * 60L + m) * 60 + s + us/1000.;
else {
/* parse error */
t = 0;
}
If that's not efficient enough, here's something down-and-dirty, eschewing scanf:
2.
#define Ctod(c) ((c) - '0')
int h = 10 * Ctod(utctime[0]) + Ctod(utctime[1]);
int m = 10 * Ctod(utctime[2]) + Ctod(utctime[3]);
int s = 10 * Ctod(utctime[4]) + Ctod(utctime[5]);
int us = 100 * Ctod(utctime[6]) + 10 * Ctod(utctime[7]) + Ctod(utctime[8]);
double t = (h * 60L + m) * 60 + s + us/1000.;
In either case, once you've got your t values, just subtract and compare as usual.
If you don't want to use floating point, change t to long int, change the scaling factors as appropriate, and compare your final difference to 400 instead of 0.4.
(But with all of this said, I wouldn't worry about efficiency too much. 10 Hz may sound fast to a pitiful human, but a tenth of a second is a hell of a long time for a decent computer.)
I have attempted to implement the sine function in C, yet I am getting weird results. Here are the three functions I am using to calculate sine:
#define PI 3.14159265358979323846
#define DEPTH 16
double sine(long double);
long double pow(long double, unsigned int);
unsigned int fact(unsigned int);
double sine(long double x) {
long double i_x = x *= PI/180;
int n = 3, d = 0, sign = -1;
// fails past 67 degrees
for (; d < DEPTH; n += 2, d++, sign *= -1) {
x += pow(i_x, n) / fact(n) * sign;
}
return x;
}
long double pow(long double base, unsigned int exp) {
double answer = 1;
while (exp) {
answer *= base;
exp--;
}
return answer;
}
unsigned int fact(unsigned int n) {
unsigned int answer = 1;
while (n > 1) {
answer *= n--;
}
return answer;
}
To test it I have been testing it against the built in sine function as follows:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
main() {
for (int i = 0; i <= 180; i++) {
printf("sin(%i) = %lf, %lf\n", i, sine(i), sin(i*3.14159265358979323846/180));
}
exit(EXIT_SUCCESS);
}
Up through 67 degrees, it calculates the same as the built in function. Though, as it increases past 67 it typically strays gets further and further from the actual value.
Here is an example output:
>> sin(100) = 0.987711, 0.984808
>> sin(101) = 0.986885, 0.981627
>> sin(102) = 0.987056, 0.978148
>> sin(103) = 0.988830, 0.974370
>> sin(104) = 0.993060, 0.970296
>> sin(105) = 1.000948, 0.965926
>> sin(106) = 1.014169, 0.961262
>> sin(107) = 1.035052, 0.956305
>> sin(108) = 1.066807, 0.951057
>> sin(109) = 1.113846, 0.945519
>> sin(110) = 1.182194, 0.939693
>> sin(111) = 1.280047, 0.933580
>> sin(112) = 1.418502, 0.927184
>> sin(113) = 1.612527, 0.920505
>> sin(114) = 1.882224, 0.913545
>> sin(115) = 2.254492, 0.906308
>> sin(116) = 2.765192, 0.898794
>> sin(117) = 3.461969, 0.891007
...
>> sin(180) = 8431648.192239, 0.000000
Does anybody know why this is happening? I am using Visual Studio 2017 on Windows 7, if that provides any useful information.
Each time your for loop progresses, n is increased by 2 and hence for DEPTH = 16, near the end of loop you have to calculate factorials of numbers as big as 30 and you are using unsigned int that can only store values as big as 2^32 = 4294967296 ~= 12! and this causes overflow in your factorial function which in turn gives you the wrong factorial.
Even if you used long double for it and I already stated in my comments that long double in MSCRT is mapped to double (Reference) you'd still see some anomalies probably at larger angles because although double can store values as big as 1.8E+308 but it loses its granularity at 2^53 = 9007199254740992 ~= 18! (i.e. 2^53 + 1 stored as a double is equal to 2^53). So once you go up in angles, the effect of this behavior becomes larger and larger to the point that it is noticeable in the 6 decimal precision that you are using with printf().
Although you are on the right track, you should use a bignum library like GMP or libcrypto. They can perform these calculations without the loss of precision.
BTW, since your are developing on Windows 7 that means you are either using x86 or x86-64. On these platforms, x87 is capable of performing extended precision (as per 754 standard) operations with 80 bits but I am unaware of compiler intrinsics that can give you that capability without resorting to assembly code.
I also would like to direct your attention to range reduction techniques. Although I still recommend using bignum libs, if you are good between 0 and 90 degrees (0 and 45 if I'm to be more strict), you can compute the sine() of all other angles just by simple trigonometric identities.
UPDATE:
Actually I'm gonna correct myself about using doubles in factorial calculations. After writing a simple program I verified that when I usedouble to store factorials, they are correct even if I go upper than 18. After giving it some thought I realized that in the case of factorials, the situation with double's granularity is a little bit more complex. I'll give you an example to make it clear:
19! = 19 * 18 * ... * 2 * 1
in this number 18, 16, 14, ... , 2 are all multiples of 2 and since a multiplication by 2 is equivalent to a shift to the left in binary representation, all lower bits in 19! are already 0 and hence when double's rounding kicks in for integers greater than 2^53, these factorials are unaffected. You can compute the number of least significant zeroes in the binary representation of 19! by counting the number of 2's which is 16. (for 20!, it is 18)
I'm gonna go up to 1.8e+308 and check if all the factorials are unaffected or not. I'll update you with the results.
UPDATE 2:
If we use doubles to hold factorials, they are affected by rounding from 23! onward. It can be easily shown, because 2^74 < 23! < 2^75 which means that at least 75 bits of precision is required to represent it, but since 23! has 19 least significant bits with the value of 0, so it needs 75 - 19 = 56 which is larger than 53 bits provided by double.
For 22!, it is 51 bits (you can calculate it yourself).
There are multiple issues in your code:
You redefine the standard function pow() with a different prototype. This may cause problems when you link the program as en executable. Use a different anme such as pow_int.
You should define the pow_int and fact functions as static before the sine function. It may allow for better optimisation at compile time.
Indeed fact is limited by the range of type unsigned int which is much less than the precision of type long double. Factorials beyond 12 have an incorrect value, causing a loss of precision.
You could actually compute the terms incrementally, saving a lot of computations and avoiding potential loss of precision.
The prototype for main() without arguments is int main(void)
The computation of PI/180 is performed as double, which is less precise than long double. You should write the expression as x = x * PI / 180;
DEPTH should be increased to improve the precision. At least 4 more terms bring a substantial improvement.
You should apply a range reduction: taking advantage of the sine function symmetric and periodic nature, computation can be performed with fewer terms on x modulo 90 or even 45 degrees.
Here is a modified version:
#include <stdio.h>
#include <math.h>
#define PI_L 3.14159265358979323846264338327950288L
#define PI 3.14159265358979323846264338327950288
#define DEPTH 24
double sine(long double x) {
long double res, term, x2, t1;
int phase;
x = remquol(x, 90, &phase);
if (phase & 1)
x = 90 - x;
x = x * PI_L / 180; // convert x to radians
x2 = x * x; // pre-compute x^2
// compute the sine series: x - x^3/3! + x^5/5! ...
res = term = x; // the first term is x
for (int n = 1; n < DEPTH; n += 4) {
// to reduce precision loss, compute 2 terms for each iteration
t1 = term * x2 / ((n + 1) * (n + 2));
term = t1 * x2 / ((n + 3) * (n + 4));
// update the result with the difference of the terms
res += term - t1;
}
if (phase & 2)
res = -res;
return (double)res;
}
int main(void) {
printf("deg sin sine delta\n\n");
for (int i = 0; i <= 360; i += 10) {
double s1 = sin(i * PI / 180);
double s2 = sine(i);
printf("%3i %20.17f %20.17f %g\n", i, s1, s2, s2 - s1);
}
return 0;
}
The output is:
deg sin sine delta
0 0.00000000000000000 0.00000000000000000 0
10 0.17364817766693033 0.17364817766693036 2.77556e-17
20 0.34202014332566871 0.34202014332566871 0
30 0.49999999999999994 0.50000000000000000 5.55112e-17
40 0.64278760968653925 0.64278760968653936 1.11022e-16
50 0.76604444311897801 0.76604444311897801 0
60 0.86602540378443860 0.86602540378443860 0
70 0.93969262078590832 0.93969262078590843 1.11022e-16
80 0.98480775301220802 0.98480775301220802 0
90 1.00000000000000000 1.00000000000000000 0
100 0.98480775301220802 0.98480775301220802 0
110 0.93969262078590843 0.93969262078590843 0
120 0.86602540378443882 0.86602540378443860 -2.22045e-16
130 0.76604444311897812 0.76604444311897801 -1.11022e-16
140 0.64278760968653947 0.64278760968653936 -1.11022e-16
150 0.49999999999999994 0.50000000000000000 5.55112e-17
160 0.34202014332566888 0.34202014332566871 -1.66533e-16
170 0.17364817766693025 0.17364817766693036 1.11022e-16
180 0.00000000000000012 -0.00000000000000000 -1.22465e-16
190 -0.17364817766693047 -0.17364817766693036 1.11022e-16
200 -0.34202014332566866 -0.34202014332566871 -5.55112e-17
210 -0.50000000000000011 -0.50000000000000000 1.11022e-16
220 -0.64278760968653925 -0.64278760968653936 -1.11022e-16
230 -0.76604444311897790 -0.76604444311897801 -1.11022e-16
240 -0.86602540378443837 -0.86602540378443860 -2.22045e-16
250 -0.93969262078590821 -0.93969262078590843 -2.22045e-16
260 -0.98480775301220802 -0.98480775301220802 0
270 -1.00000000000000000 -1.00000000000000000 0
280 -0.98480775301220813 -0.98480775301220802 1.11022e-16
290 -0.93969262078590854 -0.93969262078590843 1.11022e-16
300 -0.86602540378443860 -0.86602540378443860 0
310 -0.76604444311897812 -0.76604444311897801 1.11022e-16
320 -0.64278760968653958 -0.64278760968653936 2.22045e-16
330 -0.50000000000000044 -0.50000000000000000 4.44089e-16
340 -0.34202014332566855 -0.34202014332566871 -1.66533e-16
350 -0.17364817766693127 -0.17364817766693036 9.15934e-16
360 -0.00000000000000024 0.00000000000000000 2.44929e-16
As can be seen above, the sine() function seems more precise than the standard sin function on my system: sin(180 * M_PI / 128) should be 0 precisely. Similarly, sin(150 * M_PI / 128) should be 0.5 exactly.
Your way of polynomial series evaluation is numerically unstable. Try horner's method which is more stable than power calculations.
Your problem is here:
for (; d < DEPTH; n += 2, d++, sign *= -1) {
x += pow(i_x, n) / fact(n) * sign;
}
You are using d < DEPTH in error when it should be n < DEPTH, d is irrelevant to your computations within the loop. The following should work -- although I have not compiled to test.
for (; n < DEPTH; n += 2, sign *= -1) {
x += pow(i_x, n) / fact(n) * sign;
}
note: a DEPTH of 12 (e.g. Taylor Series expansion with terms 1, 3, 5, ... 11) is sufficient for 3e-10 error -- 3 ten billionths at 60-degrees. (though error increases as angle increases between 0-360, a DEPTH of 20 will keep error less than 1.0e-8 over the entire range.)
Enabling compiler warnings would have caught the unused d in sine.
Here is an example of code with the changes (note: Gnu provides a constant M_PI for PI):
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#define DEPTH 16
/* n factorial */
uint64_t nfact (int n)
{
if (n <= 0) return 1;
uint64_t s = n;
while (--n)
s *= n;
return s;
}
/* y ^ x */
double powerd (const double y, const int x)
{
if (!x) return 1;
double r = y;
for (int i = 1; i < x; i++)
r *= y;
return r;
}
double sine (double deg)
{
double rad = deg * M_PI / 180.0,
x = rad;
int sign = -1;
for (int n = 3; n < DEPTH; n += 2, sign *= -1)
x += sign * powerd (rad, n) / nfact (n);
return x;
}
int main (void) {
printf (" deg sin sine\n\n");
for (int i = 0; i < 180; i++)
printf ("%3d %11.8f %11.8f\n", i, sin (i * M_PI / 180.0), sine (i));
return 0;
}
Example Use/Output
$ ./bin/sine
deg sin sine
0 0.00000000 0.00000000
1 0.01745241 0.01745241
2 0.03489950 0.03489950
3 0.05233596 0.05233596
4 0.06975647 0.06975647
5 0.08715574 0.08715574
6 0.10452846 0.10452846
7 0.12186934 0.12186934
8 0.13917310 0.13917310
9 0.15643447 0.15643447
10 0.17364818 0.17364818
11 0.19080900 0.19080900
12 0.20791169 0.20791169
13 0.22495105 0.22495105
14 0.24192190 0.24192190
15 0.25881905 0.25881905
16 0.27563736 0.27563736
17 0.29237170 0.29237170
18 0.30901699 0.30901699
19 0.32556815 0.32556815
20 0.34202014 0.34202014
21 0.35836795 0.35836795
22 0.37460659 0.37460659
23 0.39073113 0.39073113
24 0.40673664 0.40673664
25 0.42261826 0.42261826
26 0.43837115 0.43837115
27 0.45399050 0.45399050
28 0.46947156 0.46947156
29 0.48480962 0.48480962
30 0.50000000 0.50000000
31 0.51503807 0.51503807
32 0.52991926 0.52991926
33 0.54463904 0.54463904
34 0.55919290 0.55919290
35 0.57357644 0.57357644
36 0.58778525 0.58778525
37 0.60181502 0.60181502
38 0.61566148 0.61566148
39 0.62932039 0.62932039
40 0.64278761 0.64278761
41 0.65605903 0.65605903
42 0.66913061 0.66913061
43 0.68199836 0.68199836
44 0.69465837 0.69465837
45 0.70710678 0.70710678
46 0.71933980 0.71933980
47 0.73135370 0.73135370
48 0.74314483 0.74314483
49 0.75470958 0.75470958
50 0.76604444 0.76604444
51 0.77714596 0.77714596
52 0.78801075 0.78801075
53 0.79863551 0.79863551
54 0.80901699 0.80901699
55 0.81915204 0.81915204
56 0.82903757 0.82903757
57 0.83867057 0.83867057
58 0.84804810 0.84804810
59 0.85716730 0.85716730
60 0.86602540 0.86602540
61 0.87461971 0.87461971
62 0.88294759 0.88294759
63 0.89100652 0.89100652
64 0.89879405 0.89879405
65 0.90630779 0.90630779
66 0.91354546 0.91354546
67 0.92050485 0.92050485
68 0.92718385 0.92718385
69 0.93358043 0.93358043
70 0.93969262 0.93969262
71 0.94551858 0.94551858
72 0.95105652 0.95105652
73 0.95630476 0.95630476
74 0.96126170 0.96126170
75 0.96592583 0.96592583
76 0.97029573 0.97029573
77 0.97437006 0.97437006
78 0.97814760 0.97814760
79 0.98162718 0.98162718
80 0.98480775 0.98480775
81 0.98768834 0.98768834
82 0.99026807 0.99026807
83 0.99254615 0.99254615
84 0.99452190 0.99452190
85 0.99619470 0.99619470
86 0.99756405 0.99756405
87 0.99862953 0.99862953
88 0.99939083 0.99939083
89 0.99984770 0.99984770
90 1.00000000 1.00000000
91 0.99984770 0.99984770
92 0.99939083 0.99939083
93 0.99862953 0.99862953
94 0.99756405 0.99756405
95 0.99619470 0.99619470
96 0.99452190 0.99452190
97 0.99254615 0.99254615
98 0.99026807 0.99026807
99 0.98768834 0.98768834
100 0.98480775 0.98480775
101 0.98162718 0.98162718
102 0.97814760 0.97814760
103 0.97437006 0.97437006
104 0.97029573 0.97029573
105 0.96592583 0.96592583
106 0.96126170 0.96126170
107 0.95630476 0.95630476
108 0.95105652 0.95105652
109 0.94551858 0.94551858
110 0.93969262 0.93969262
111 0.93358043 0.93358043
112 0.92718385 0.92718385
113 0.92050485 0.92050485
114 0.91354546 0.91354546
115 0.90630779 0.90630779
116 0.89879405 0.89879405
117 0.89100652 0.89100652
118 0.88294759 0.88294759
119 0.87461971 0.87461971
120 0.86602540 0.86602540
121 0.85716730 0.85716730
122 0.84804810 0.84804810
123 0.83867057 0.83867057
124 0.82903757 0.82903757
125 0.81915204 0.81915204
126 0.80901699 0.80901699
127 0.79863551 0.79863551
128 0.78801075 0.78801075
129 0.77714596 0.77714596
130 0.76604444 0.76604444
131 0.75470958 0.75470958
132 0.74314483 0.74314482
133 0.73135370 0.73135370
134 0.71933980 0.71933980
135 0.70710678 0.70710678
136 0.69465837 0.69465836
137 0.68199836 0.68199835
138 0.66913061 0.66913060
139 0.65605903 0.65605902
140 0.64278761 0.64278760
141 0.62932039 0.62932038
142 0.61566148 0.61566146
143 0.60181502 0.60181501
144 0.58778525 0.58778523
145 0.57357644 0.57357642
146 0.55919290 0.55919288
147 0.54463904 0.54463901
148 0.52991926 0.52991924
149 0.51503807 0.51503804
150 0.50000000 0.49999996
151 0.48480962 0.48480958
152 0.46947156 0.46947152
153 0.45399050 0.45399045
154 0.43837115 0.43837109
155 0.42261826 0.42261820
156 0.40673664 0.40673657
157 0.39073113 0.39073105
158 0.37460659 0.37460651
159 0.35836795 0.35836786
160 0.34202014 0.34202004
161 0.32556815 0.32556804
162 0.30901699 0.30901686
163 0.29237170 0.29237156
164 0.27563736 0.27563720
165 0.25881905 0.25881887
166 0.24192190 0.24192170
167 0.22495105 0.22495084
168 0.20791169 0.20791145
169 0.19080900 0.19080873
170 0.17364818 0.17364788
171 0.15643447 0.15643414
172 0.13917310 0.13917274
173 0.12186934 0.12186895
174 0.10452846 0.10452803
175 0.08715574 0.08715526
176 0.06975647 0.06975595
177 0.05233596 0.05233537
178 0.03489950 0.03489886
179 0.01745241 0.01745170
Error Check based on DEPTH
In response to the comment regarding computing the error, you investigate the error associated with Taylor-Series expansions for both sin and cos base on the number of terms by varying DEPTH and setting an max error of EMAX 1.0e-8 using something similar to the following for the range of 0-360 (or 0-2PI),
#define DEPTH 20
#define EMAX 1.0e-8
...
/* sine as above */
...
/* cos with taylor series expansion to n = DEPTH */
long double cose (const long double deg)
{
long double rad = deg * M_PI / 180.0,
x = 1.0;
int sign = -1;
for (int n = 2; n < DEPTH; n += 2, sign *= -1)
x += sign * powerd (rad, n) / nfact (n);
return x;
}
int main (void) {
for (int i = 0; i < 180; i++) {
long double sinlibc = sin (i * M_PI / 180.0),
coslibc = cos (i * M_PI / 180.0),
sints = sine (i),
costs = cose (i),
serr = fabs (sinlibc - sints),
cerr = fabs (coslibc - costs);
if (serr > EMAX)
fprintf (stderr, "sine error exceeds limit of %e\n"
"%3d %11.8Lf %11.8Lf %Le\n",
EMAX, i, sinlibc, sints, serr);
if (cerr > EMAX)
fprintf (stderr, "cose error exceeds limit of %e\n"
"%3d %11.8Lf %11.8Lf %Le\n",
EMAX, i, coslibc, costs, cerr);
}
return 0;
}
If you check, you will find that for anything less than DEPTH 20 (10 terms in each expansion), error will exceed 1.0e-8 for higher angles. Surprisingly, the expansions are very accurate over the first quadrant for values of DEPTH as low as 12 (6-terms).
Addemdum - Improved Taylor-Series Accuracy Using 0-90 & Quadrants
In the normal Taylor-Series expansion, error grows as angle grows. And... because some just can't not tinker, I wanted to further compare accuracy between the libc sin/cos and the Taylor-Series if computations were limited to 0-90 degrees and the remainder of the period from 90-360 were handled by quadrant (2, 3 & 4) mirroring of results from 0-90. It works -- marvelously.
For example, the results of handing only angles 0-90 and bracketing angles between 90 - 180, 180 - 270 and 270 - 360 with an initial angle % 360 produces results comparable to the libc math lib functions. The maximum error between the libc and 8 & 10 term Taylor-Series expansions were, respectably:
Max Error from libc sin/cos
With TSLIM 16
sine_ts max err at : 90.00 deg -- 6.023182e-12
cose_ts max err at : 270.00 deg -- 6.513370e-11
With TSLIM 20
sine_ts max err at : 357.00 deg -- 5.342948e-16
cose_ts max err at : 270.00 deg -- 3.557149e-15
(with a large number of angles showing no difference at all)
The tweaked versions of sine and cose with Taylor-Series were as follows:
double sine (const double deg)
{
double fp = deg - (int64_t)deg, /* save fractional part of deg */
qdeg = (int64_t)deg % 360, /* get equivalent 0-359 deg angle */
rad, sine_deg; /* radians, sine_deg */
int pos_quad = 1, /* positive quadrant flag 1,2 */
sign = -1; /* taylor series term sign */
qdeg += fp; /* add fractional part back to angle */
/* get equivalent 0-90 degree angle, set pos_quad flag */
if (90 < qdeg && qdeg <= 180) /* in 2nd quadrant */
qdeg = 180 - qdeg;
else if (180 < qdeg && qdeg <= 270) { /* in 3rd quadrant */
qdeg = qdeg - 180;
pos_quad = 0;
}
else if (270 < qdeg && qdeg <= 360) { /* in 4th quadrant */
qdeg = 360 - qdeg;
pos_quad = 0;
}
rad = qdeg * M_PI / 180.0; /* convert to radians */
sine_deg = rad; /* save copy for computation */
/* compute Taylor-Series expansion for sine for TSLIM / 2 terms */
for (int n = 3; n < TSLIM; n += 2, sign *= -1) {
double p = rad;
uint64_t f = n;
for (int i = 1; i < n; i++) /* pow */
p *= rad;
for (int i = 1; i < n; i++) /* nfact */
f *= i;
sine_deg += sign * p / f; /* Taylor-series term */
}
return pos_quad ? sine_deg : -sine_deg;
}
and for cos
double cose (const double deg)
{
double fp = deg - (int64_t)deg, /* save fractional part of deg */
qdeg = (int64_t)deg % 360, /* get equivalent 0-359 deg angle */
rad, cose_deg = 1.0; /* radians, cose_deg */
int pos_quad = 1, /* positive quadrant flag 1,4 */
sign = -1; /* taylor series term sign */
qdeg += fp; /* add fractional part back to angle */
/* get equivalent 0-90 degree angle, set pos_quad flag */
if (90 < qdeg && qdeg <= 180) { /* in 2nd quadrant */
qdeg = 180 - qdeg;
pos_quad = 0;
}
else if (180 < qdeg && qdeg <= 270) { /* in 3rd quadrant */
qdeg = qdeg - 180;
pos_quad = 0;
}
else if (270 < qdeg && qdeg <= 360) /* in 4th quadrant */
qdeg = 360 - qdeg;
rad = qdeg * M_PI / 180.0; /* convert to radians */
/* compute Taylor-Series expansion for sine for TSLIM / 2 terms */
for (int n = 2; n < TSLIM; n += 2, sign *= -1) {
double p = rad;
uint64_t f = n;
for (int i = 1; i < n; i++) /* pow */
p *= rad;
for (int i = 1; i < n; i++) /* nfact */
f *= i;
cose_deg += sign * p / f; /* Taylor-series term */
}
return pos_quad ? cose_deg : -cose_deg;
}
Rabbit-trail end found...
Changing the angle range in main to -90 to 90 will still cover the whole sine range. but as the Taylor serie is starting from zero the DEPTH value can be reduced to 7. As earlier mentioned, making the fact function 64 bits unsigned
will fix the 67 degree problem.
I'm writing this function that takes in 2 numbers ref and data and checks if data is within 5% of ref.
Example: if ref is 100 and data is 102, it returns 1.
int within_5_percent(int ref, int data)
{
int result = 0;
int lower_bound = (ref - 0.05 * ref);
int upper_bound = (ref + 0.05 * ref);
// printf("Upper: %d\n",upper_bound);
// printf("Lower: %d\n", lower_bound);
if(data >= lower_bound && data <= upper_bound)
{
result = 1;
}
else
{
result = 0;
}
return result;
}
The problem I'm having is at lower_bound. When I pass 100 as ref, the upper_bound is 105 but for some reason lower_bound is 94 when it should really be 95.
To convert this into integer arithmetic, we have ref - 0.05 * ref = 0.95 * ref = 19/20 * ref, and similarly ref + 0.05 * ref = 21/20 * ref.
So we want to check whether 19/20 * ref ≤ data ≤ 21/20 * ref, or in other words whether 19 * ref <= 20 * data && 20 * data <= 21 * ref. The code then becomes
int within_5_percent(int ref, int data)
{
int result = 0;
// printf("Upper: %d\n",upper_bound);
// printf("Lower: %d\n", lower_bound);
if(20 * data >= 19 * ref && 20 * data <= 21 * ref)
{
result = 1;
}
else
{
result = 0;
}
return result;
}
Note that any problems with floating-point arithmetic are gone. However, you could have problems with integer overflow if ref and data are too big (i.e. positive) or too small (i.e. negative).
lower_bound takes on the value of 94 due to 0.05 not being exactly representable as a double and conversion back to int truncates the fraction.
int lower_bound = (int) 100 - (double) 0.05 * (int) 100 -->
int lower_bound = 100 - 0.05000000000000000277... * 100 -->
int lower_bound = 94.999....999... -->
int lower_bound = 94;
A simple alternative using only integer math.
int within5(int ref, int data) {
int lo = ref - ref/20;
int hi = ref + ref/20;
return (lo <= data && data <= hi);
}
As the above and various other answers fail with a negative ref or with large values, following is a more secure method that I believe works for all ref,data.
int within5secure(int ref, int data) {
int ref20 = abs(ref / 20);
int lo = ref > INT_MIN + ref20 ? ref - ref20 : INT_MIN;
int hi = ref < INT_MAX - ref20 ? ref + ref20 : INT_MAX;
return (lo <= data && data <= hi);
}
0.05 * ref triggers C's type promotion rules and evaluates to a double.
ref - 0.05 * ref then does the same thing, so the output of (ref - 0.05 * ref) is a double.
The effect of int lower_bound = (ref - 0.05 * ref); is then to assign a double to an int, which is performed by truncation. Which, for a positive floating point number, means rounding down.
Therefore all you're suffering from is rounding error. You could use round(ref - 0.05 * ref) to get the nearest integer rather than the one below it, or you could perform the whole calculation in integers, e.g. lower_bound = (ref * 95) / 100;
0.05 * ref
Will convert the result to double. It cannot be directly represended as a floating point number so the actual result is something like 5.000000001.
100 - 5.000001 = 94.99999999
Which is then truncated to 94.
It depends how you want to solve this, but you could for example multiply by 5 and then divide by 100 to get 5%. But here you still have to define how to round the result after the division.
Just change int lower_bound and int upper_bound to float lower_bound and float upper_bound in your code because you might end up with decimal answers when you calculate lower_bound = (ref - 0.05 * ref) and upper_bound = (ref + 0.05 *ref). For example when ref=90 your upper_bound will be 94.5 and lower_bound will be 85.5.
int within_5_percent(int ref, int data)
{
int result = 0;
float lower_bound = (ref - 0.05 * ref);
float upper_bound = (ref + 0.05 * ref);
// printf("Upper: %f\n",upper_bound);
// printf("Lower: %f\n", lower_bound);
if(data >= lower_bound && data <= upper_bound)
{
result = 1;
}
else
{
result = 0;
}
return result;
}
For my studies, I have to code an algorithm to calculate sin() with this function:
However, in my algorithm, I have to keep the value of X between 0 and Pi/2. So, I wrote my algorithm but all the results are wrong.
Here is my code:
double sinX(double x){
double resultat = 0;
int i;
if(x < 0 || x > M_PI_2)
x = fmod(x,M_PI_2);
for(i = 1;i<=30;i++){
resultat += -1 * ((x*x)/(2*i*(2*i+1)))*(pow(-1,i-1))*((pow(x,2*i-1))/(factorielle(2*i-1)));
}
return resultat;
}
I didn't find the reason. Can you help me?
Here the are few values of X and the result with fmod
1 / 1
2 / 0.429204
3 / 1.4292
4 / 0.858407
5 / 0.287611
6 / 1.28761
7 / 0.716815
8 / 0.146018
9 / 1.14602
10 / 0.575222
11 / 0.00442571
12 / 1.00443
13 / 0.433629
14 / 1.43363
15 / 0.862833
16 / 0.292037
17 / 1.29204
18 / 0.72124
19 / 0.150444
20 / 1.15044
and the result with the algorithm
1 / -0.158529
2 / -0.0130568
3 / -0.439211
4 / -0.101605
5 / -0.00394883
6 / -0.327441
7 / -0.0598281
8 / -0.000518332
9 / -0.234888
10 / -0.0312009
11 / -1.44477e-008
12 / -0.160572
13 / -0.0134623
14 / -0.443022
15 / -0.103145
16 / -0.00413342
17 / -0.330639
18 / -0.0609237
19 / -0.000566869
20 / -0.237499
Here is my "factorielle" definition
double factorielle(double x){
double resultat = 1;
int i;
if(x != 0){
for (i=2;i<=x;i++)
{
resultat *= i;
}
}
else{
resultat = 1;
}
return resultat;
}
And values :
1 / 1
2 / 2
3 / 6
4 / 24
5 / 120
6 / 720
7 / 5040
8 / 40320
9 / 362880
10 / 3.6288e+006
11 / 3.99168e+007
12 / 4.79002e+008
13 / 6.22702e+009
14 / 8.71783e+010
15 / 1.30767e+012
16 / 2.09228e+013
17 / 3.55687e+014
18 / 6.40237e+015
19 / 1.21645e+017
20 / 2.4329e+018
You're misunderstanding the purpose of the second formula you show. The idea is that you use that formula to compute each term in the sum from the preceding term, saving you from the need to use any pow or factorial calls.
#include <stdio.h>
double sinX(double x) {
double term, total_so_far;
int i;
term = x; /* First term in the expansion. */
total_so_far = 0.0;
for (i = 1; i <= 30; i++) {
/* Add current term to sum. */
total_so_far += term;
/* Compute next term from the current one. */
term *= -(x * x) / (2*i) / (2*i + 1);
}
return total_so_far;
}
int main(void) {
/* testing */
double x;
int i;
for (i = 0; i <= 10; i++) {
x = i / 10.0;
printf("sin(%f) is %f\n", x, sinX(x));
}
return 0;
}
And the results of running this code, on my machine:
sin(0.000000) is 0.000000
sin(0.100000) is 0.099833
sin(0.200000) is 0.198669
sin(0.300000) is 0.295520
sin(0.400000) is 0.389418
sin(0.500000) is 0.479426
sin(0.600000) is 0.564642
sin(0.700000) is 0.644218
sin(0.800000) is 0.717356
sin(0.900000) is 0.783327
sin(1.000000) is 0.841471
That should give you reasonable results for the range 0 to pi / 2. Outside that range you'll need to be a bit cleverer about the reduction you're using: simply reducing modulo pi / 2 won't give correct results. (Hint: it's safe to reduce modulo 2 * pi, since the sin function is periodic with period 2 * pi. Now use symmetries of the sin function to reduce to the range 0 to pi / 2.)
EDIT An explanation of why the current code is giving incorrect results: apart from the flawed reduction step, in your sum you start with the term i = 1. But the first term should be for i = 0 (that's the x term, while the i=1 term is the -x^3 / 3! term). A quick and dirty fix is to remove the reduction step, and to initialise your resultat variable to x rather than 0. That should give you good results for small x, and then you can figure out how to replace the reduction step. I'd be surprised if you were really intended to compute the answer using explicit factorial and power calls, though - I'm almost sure that you're expected to compute each term from the preceding one as described above.
There are two problems with your code:
sin(x+k*π/2) does not necessarily equal sin(x)
Your expression for the term is a little bit messed up. The instructions seem to suggest that you calculate the next term in the series from the previous term. Start with the value for i=0 and then use the equation in your question to compute the next term in each iteration.
Finally, I followed yours indications. Here is my final code :
double sinX(double x)
{
double result = 1.0;
double term_i = 1.0;
int i = 2;
x = fmod(x, 2*M_PI);
for(i = 2; i<= 30; i+=2)
{
term_i = (-term_i * (x*x)) / (i*(i+1));
result += term_i;
}
return x * result;
}
Idea about the number of terms with OP's posted answer.
As long as one performs some range limitation first, like fmod(), the number of terms needed can be reasonably determined dynamically. (Uses 1 to 23 iterations for x: 0 to 2*pi.)
double sinX1(double x)
{
double result = 1.0;
double term_i = 1.0;
int i = 2;
x = fmod(x, 2*M_PI);
// for(i = 2; i<= 30; i+=2)
for(i = 2; ((1.0 + term_i) != 1.0); i+=2)
{
term_i = (-term_i * (x*x)) / (i*(i+1));
result += term_i;
}
return x * result;
}