Complex numbers (complex.h) and apparent lag of precision - c

I decided to play around a bit with complex.h, and ran into what I consider a very curious problem.
int mandelbrot(long double complex c, int lim)
{
long double complex z = c;
for(int i = 0; i < lim; ++i, z = cpowl(z,2)+c)
{
if(creall(z)*creall(z)+cimagl(z)*cimagl(z) > 4.0)
return 0;
}
return 1;
}
int mandelbrot2(long double cr, long double ci, int lim)
{
long double zr = cr;
long double zi = ci;
for(int i = 0; i < lim; ++i, zr = zr*zr-zi*zi+cr, zi = 2*zr*zi+ci)
{
if(zr*zr+zi*zi > 4.0)
return 0;
}
return 1;
}
These functions do not behave the same. If we input -2.0+0.0i and a limit higher than 17, the latter will return 1, which is correct for any limit, while the former will return 0, at least on my system. GCC 9.1.0, Ryzen 2700x.
I cannot for the life of me figure out how this can happen. I mean while I may not entirely understand how complex.h works behind the scenes, for this particular example it makes no sense that the results should deviate like this.
While writing I notices the cpowl(z,2)+c, and tried to change it to z*z+c, which helped, however after a quick test, I found that the behavior still differ. Ex. -1.3+0.1*I, lim=18.
I'm curious to know if this is specific to my system and what the cause might be, though I'm perfectly aware that the most like scenario is me having made a mistake, but alas, I can't find it.
--- edit---
Finally, the complete code, including alterations and fixes. The two functions now seem to yield the same result.
#include <stdio.h>
#include <complex.h>
int mandelbrot(long double complex c, int lim)
{
long double complex z = c;
for(int i = 0; i < lim; ++i, z = z*z+c)
{
if(creall(z)*creall(z)+cimagl(z)*cimagl(z) > 4.0)
return 0;
}
return 1;
}
int mandelbrot2(long double cr, long double ci, int lim)
{
long double zr = cr;
long double zi = ci;
long double tmp;
for(int i = 0; i < lim; ++i)
{
if(zr*zr+zi*zi > 4.0) return 0;
tmp = zi;
zi = 2*zr*zi+ci;
zr = zr*zr-tmp*tmp+cr;
}
return 1;
}
int main()
{
long double complex c = -2.0+0.0*I;
printf("%i\n",mandelbrot(c,100));
printf("%i\n",mandelbrot2(-2.0,0.0,100));
return 0;
}
cpowl() still messes things up, but I suppose if I wanted to, I could just create my own implementation.

The second function is the one that's incorrect, not the first.
In the expression in the third clause of the for:
zr = zr*zr-zi*zi+cr, zi = 2*zr*zi+ci
The calculation of zi is using the new value of zr, not the current one. You'll need to save the results of these two calculations in temp variables, then assign these back to zr and zi:
int mandelbrot2(long double cr, long double ci, int lim)
{
long double zr = cr;
long double zi = ci;
for(int i = 0; i < lim; ++i)
{
printf("i=%d, z=%Lf%+Lfi\n", i, zr, zi);
if(zr*zr+zi*zi > 4.0)
return 0;
long double new_zr = zr*zr-zi*zi+cr;
long double new_zi = 2*zr*zi+ci;
zr = new_zr;
zi = new_zi;
}
return 1;
}
Also, using cpowl for simple squaring will result in inaccuracies that can be avoided by simplying using z*z in this case.

Difference for Input −2 + 0 i
cpowl is inaccurate. Exponentiation is a complicated function to implement, and a variety of errors likely arise in its computation. On macOS 10.14.6, z in the mandelbrot routine takes on these values in successive iterations:
z = -2 + 0 i.
z = 2 + 4.33681e-19 i.
z = 2 + 1.73472e-18 i.
z = 2 + 6.93889e-18 i.
z = 2 + 2.77556e-17 i.
z = 2 + 1.11022e-16 i.
z = 2 + 4.44089e-16 i.
z = 2 + 1.77636e-15 i.
z = 2 + 7.10543e-15 i.
z = 2 + 2.84217e-14 i.
z = 2 + 1.13687e-13 i.
z = 2 + 4.54747e-13 i.
z = 2 + 1.81899e-12 i.
z = 2 + 7.27596e-12 i.
z = 2 + 2.91038e-11 i.
z = 2 + 1.16415e-10 i.
z = 2 + 4.65661e-10 i.
Thus, once the initial error is made, producing 2 + 4.33681•10−19 i, z continues to grow (correctly, as a result of mathematics, not just floating-point errors) until it is large enough to pass the test comparing the square of its absolute value to 4. (The test does not immediately capture the excess because the square of the imaginary part is so small it is lost in rounding when added to the square of the real part.)
In contrast, if we replace z = cpowl(z,2)+c with z = z*z + c, z remains 2 (that is, 2 + 0i). In general, the operations in z*z experience some rounding errors too, but not as badly as with cpowl.
Difference for Input −1.3 + 0.1 i
For this input, the difference is caused by the incorrect calculation in the update step of the for loop:
++i, zr = zr*zr-zi*zi+cr, zi = 2*zr*zi+ci
That uses the new value of zr when calculating zi. It can be fixed by inserting long double t; and changing the update step to
++i, t = zr*zr - zi*zi + cr, zi = 2*zr*zi + ci, zr = t

Related

Logistic regression code stops working above ~43,500 generated observations

Having some difficulty troubleshooting code I wrote in C to perform a logistic regression. While it seems to work on smaller, semi-randomized datasets, it stops working (e.g. assigning proper probabilities of belonging to class 1) at around the point where I pass 43,500 observations (determined by tweaking the number of observations created. When creating the 150 features used in the code, I do create the first two as a function of the number of observations, so I'm not sure if maybe that's the issue here, though I am using double precision. Maybe there's an overflow somewhere in the code?
The below code should be self-contained; it generates m=50,000 observations with n=150 features. Setting m below 43,500 should return "Percent class 1: 0.250000", setting to 44,000 or above will return "Percent class 1: 0.000000", regardless of what max_iter (number of times we sample m observations) is set to.
The first feature is set to 1.0 divided by the total number of observations, if class 0 (first 75% of observations), or the index of the observation divided by the total number of observations otherwise.
The second feature is just index divided by total number of observations.
All other features are random.
The logistic regression is intended to use stochastic gradient descent, randomly selecting an observation index, computing the gradient of the loss with the predicted y using current weights, and updating weights with the gradient and learning rate (eta).
Using the same initialization with Python and NumPy, I still get the proper results, even above 50,000 observations.
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
// Compute z = w * x + b
double dlc( int n, double *X, double *coef, double intercept )
{
double y_pred = intercept;
for (int i = 0; i < n; i++)
{
y_pred += X[i] * coef[i];
}
return y_pred;
}
// Compute y_hat = 1 / (1 + e^(-z))
double sigmoid( int n, double alpha, double *X, double *coef, double beta, double intercept )
{
double y_pred;
y_pred = dlc(n, X, coef, intercept);
y_pred = 1.0 / (1.0 + exp(-y_pred));
return y_pred;
}
// Stochastic gradient descent
void sgd( int m, int n, double *X, double *y, double *coef, double *intercept, double eta, int max_iter, int fit_intercept, int random_seed )
{
double *gradient_coef, *X_i;
double y_i, y_pred, resid;
int idx;
double gradient_intercept = 0.0, alpha = 1.0, beta = 1.0;
X_i = (double *) malloc (n * sizeof(double));
gradient_coef = (double *) malloc (n * sizeof(double));
for ( int i = 0; i < n; i++ )
{
coef[i] = 0.0;
gradient_coef[i] = 0.0;
}
*intercept = 0.0;
srand(random_seed);
for ( int epoch = 0; epoch < max_iter; epoch++ )
{
for ( int run = 0; run < m; run++ )
{
// Randomly sample an observation
idx = rand() % m;
for ( int i = 0; i < n; i++ )
{
X_i[i] = X[n*idx+i];
}
y_i = y[idx];
// Compute y_hat
y_pred = sigmoid( n, alpha, X_i, coef, beta, *intercept );
resid = -(y_i - y_pred);
// Compute gradients and adjust weights
for (int i = 0; i < n; i++)
{
gradient_coef[i] = X_i[i] * resid;
coef[i] -= eta * gradient_coef[i];
}
if ( fit_intercept == 1 )
{
*intercept -= eta * resid;
}
}
}
}
int main(void)
{
double *X, *y, *coef, *y_pred;
double intercept;
double eta = 0.05;
double alpha = 1.0, beta = 1.0;
long m = 50000;
long n = 150;
int max_iter = 20;
long class_0 = (long)(3.0 / 4.0 * (double)m);
double pct_class_1 = 0.0;
clock_t test_start;
clock_t test_end;
double test_time;
printf("Constructing variables...\n");
X = (double *) malloc (m * n * sizeof(double));
y = (double *) malloc (m * sizeof(double));
y_pred = (double *) malloc (m * sizeof(double));
coef = (double *) malloc (n * sizeof(double));
// Initialize classes
for (int i = 0; i < m; i++)
{
if (i < class_0)
{
y[i] = 0.0;
}
else
{
y[i] = 1.0;
}
}
// Initialize observation features
for (int i = 0; i < m; i++)
{
if (i < class_0)
{
X[n*i] = 1.0 / (double)m;
}
else
{
X[n*i] = (double)i / (double)m;
}
X[n*i + 1] = (double)i / (double)m;
for (int j = 2; j < n; j++)
{
X[n*i + j] = (double)(rand() % 100) / 100.0;
}
}
// Fit weights
printf("Running SGD...\n");
test_start = clock();
sgd( m, n, X, y, coef, &intercept, eta, max_iter, 1, 42 );
test_end = clock();
test_time = (double)(test_end - test_start) / CLOCKS_PER_SEC;
printf("Time taken: %f\n", test_time);
// Compute y_hat and share of observations predicted as class 1
printf("Making predictions...\n");
for ( int i = 0; i < m; i++ )
{
y_pred[i] = sigmoid( n, alpha, &X[i*n], coef, beta, intercept );
}
printf("Printing results...\n");
for ( int i = 0; i < m; i++ )
{
//printf("%f\n", y_pred[i]);
if (y_pred[i] > 0.5)
{
pct_class_1 += 1.0;
}
// Troubleshooting print
if (i < 10 || i > m - 10)
{
printf("%g\n", y_pred[i]);
}
}
printf("Percent class 1: %f", pct_class_1 / (double)m);
return 0;
}
For reference, here is my (presumably) equivalent Python code, which returns the correct percent of identified classes at more than 50,000 observations:
import numpy as np
import time
def sigmoid(x):
return 1 / (1 + np.exp(-x))
class LogisticRegressor:
def __init__(self, eta, init_runs, fit_intercept=True):
self.eta = eta
self.init_runs = init_runs
self.fit_intercept = fit_intercept
def fit(self, x, y):
m, n = x.shape
self.coef = np.zeros((n, 1))
self.intercept = np.zeros((1, 1))
for epoch in range(self.init_runs):
for run in range(m):
idx = np.random.randint(0, m)
x_i = x[idx:idx+1, :]
y_i = y[idx]
y_pred_i = sigmoid(x_i.dot(self.coef) + self.intercept)
gradient_w = -(x_i.T * (y_i - y_pred_i))
self.coef -= self.eta * gradient_w
if self.fit_intercept:
gradient_b = -(y_i - y_pred_i)
self.intercept -= self.eta * gradient_b
def predict_proba(self, x):
m, n = x.shape
y_pred = np.ones((m, 2))
y_pred[:,1:2] = sigmoid(x.dot(self.coef) + self.intercept)
y_pred[:,0:1] -= y_pred[:,1:2]
return y_pred
def predict(self, x):
return np.round(sigmoid(x.dot(self.coef) + self.intercept))
m = 50000
n = 150
class1 = int(3.0 / 4.0 * m)
X = np.random.rand(m, n)
y = np.zeros((m, 1))
for obs in range(m):
if obs < class1:
continue
else:
y[obs,0] = 1
for obs in range(m):
if obs < class1:
X[obs, 0] = 1.0 / float(m)
else:
X[obs, 0] = float(obs) / float(m)
X[obs, 1] = float(obs) / float(m)
logit = LogisticRegressor(0.05, 20)
start_time = time.time()
logit.fit(X, y)
end_time = time.time()
print(round(end_time - start_time, 2))
y_pred = logit.predict(X)
print("Percent:", y_pred.sum() / len(y_pred))
The issue is here:
// Randomly sample an observation
idx = rand() % m;
... in light of the fact that the OP's RAND_MAX is 32767. This is exacerbated by the fact that all of the class 0 observations are at the end.
All samples will be drawn from the first 32768 observations, and when the total number of observations is greater than that, the proportion of class 0 observations among those that can be sampled is less than 0.25. At 43691 total observations, there are no class 0 observations among those that can be sampled.
As a secondary issue, rand() % m does not yield a wholly uniform distribution if m does not evenly divide RAND_MAX + 1, though the effect of this issue will be much more subtle.
Bottom line: you need a better random number generator.
At minimum, you could consider combining the bits from two calls to rand() to yield an integer with sufficient range, but you might want to consider getting a third-party generator. There are several available.
Note: OP reports "m=50,000 observations with n=150 features.", so perhaps this is not the issue for OP, but I'll leave this answer up for reference when OP tries larger tasks.
A potential issue:
long overflow
m * n * sizeof(double) risks overflow when long is 32-bit and m*n > LONG_MAX (or about 46,341 if m, n are the same).
OP does report
A first step is to perform the multiplication using size_t math where we gain at least 1 more bit in the calculation.
// m * n * sizeof(double)
sizeof(double) * m * n
Yet unless OP's size_t is more than 32-bit, we still have trouble.
IAC, I recommend to use size_t for array sizing and indexing.
Check allocations for failure too.
Since RAND_MAX may be too small and array indexing should be done using size_t math, consider a helper function to generate a random index over the entire size_t range.
// idx = rand() % m;
size_t idx = rand_size_t() % (size_t)m;
If stuck with the standard rand(), below is a helper function to extend its range as needed.
It uses the real nifty IMAX_BITS(m).
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
// https://stackoverflow.com/a/4589384/2410359
/* Number of bits in inttype_MAX, or in any (1<<k)-1 where 0 <= k < 2040 */
#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12))
// Test that RAND_MAX is a power of 2 minus 1
_Static_assert((RAND_MAX & 1) && ((RAND_MAX/2 + 1) & (RAND_MAX/2)) == 0, "RAND_MAX is not a Mersenne number");
#define RAND_MAX_WIDTH (IMAX_BITS(RAND_MAX))
#define SIZE_MAX_WIDTH (IMAX_BITS(SIZE_MAX))
size_t rand_size_t(void) {
size_t index = (size_t) rand();
for (unsigned i = RAND_MAX_WIDTH; i < SIZE_MAX_WIDTH; i += RAND_MAX_WIDTH) {
index <<= RAND_MAX_WIDTH;
index ^= (size_t) rand();
}
return index;
}
Further considerations can replace the rand_size_t() % (size_t)m with a more uniform distribution.
As has been determined elsewhere, the problem is due to the implementation's RAND_MAX value being too small.
Assuming 32-bit ints, a slightly better PRNG function can be implemented in the code, such as this C implementation of the minstd_rand() function from C++:
#define MINSTD_RAND_MAX 2147483646
// Code assumes `int` is at least 32 bits wide.
static unsigned int minstd_seed = 1;
static void minstd_srand(unsigned int seed)
{
seed %= 2147483647;
// zero seed is bad!
minstd_seed = seed ? seed : 1;
}
static int minstd_rand(void)
{
minstd_seed = (unsigned long long)minstd_seed * 48271 % 2147483647;
return (int)minstd_seed;
}
Another problem is that expressions of the form rand() % m produce a biased result when m does not divide (unsigned int)RAND_MAX + 1. Here is an unbiased function that returns a random integer from 0 to le inclusive, making use of the minstd_rand() function defined earlier:
static int minstd_rand_max(int le)
{
int r;
if (le < 0)
{
r = le;
}
else if (le >= MINSTD_RAND_MAX)
{
r = minstd_rand();
}
else
{
int rm = MINSTD_RAND_MAX - le + MINSTD_RAND_MAX % (le + 1);
while ((r = minstd_rand()) > rm)
{
}
r /= (rm / (le + 1) + 1);
}
return r;
}
(Actually, it does still have a very small bias because minstd_rand() will never return 0.)
For example, replace rand() % 100 with minstd_rand_max(99), and replace rand() % m with minstd_rand_max(m - 1). Also replace srand(random_seed) with minstd_srand(random_seed).

e^x without math.h

I'm trying to find ex without using math.h. My code gives wrong anwsers when x is bigger or lower than ~±20. I tried to change all double types to long double types, but it gave some trash on input.
My code is:
#include <stdio.h>
double fabs1(double x) {
if(x >= 0){
return x;
} else {
return x*(-1);
}
}
double powerex(double x) {
double a = 1.0, e = a;
for (int n = 1; fabs1(a) > 0.001; ++n) {
a = a * x / n;
e += a;
}
return e;
}
int main(){
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
int n;
scanf("%d", &n);
for(int i = 0; i<n; i++) {
double number;
scanf("%lf", &number);
double e = powerex(number);
printf("%0.15g\n", e);
}
return 0;
}
Input:
8
0.0
1.0
-1.0
2.0
-2.0
100.0
-100.0
0.189376476361643
My output:
1
2.71825396825397
0.367857142857143
7.38899470899471
0.135379188712522
2.68811714181613e+043
-2.91375564689153e+025
1.20849374134639
Right output:
1
2.71828182845905
0.367879441171442
7.38905609893065
0.135335283236613
2.68811714181614e+43
3.72007597602084e-44
1.20849583696666
You can see that my answer for e−100 is absolutely incorrect. Why does my code output this? What can I do to improve this algorithm?
When x is negative, the sign of each term alternates. This means each successive sum switches widely in value rather than increasing more gradually when a positive power is used. This means that the loss in precision with successive terms has a large effect on the result.
To handle this, check the sign of x at the start. If it is negative, switch the sign of x to perform the calculation, then when you reach the end of the loop invert the result.
Also, you can reduce the number of iterations by using the following counterintuitive condtion:
e != e + a
On its face, it appears that this should always be true. However, the condition becomes false when the value of a is outside of the precision of the value of e, in which case adding a to e doesn't change the value of e.
double powerex(double x) {
double a = 1.0, e = a;
int invert = x<0;
x = fabs1(x);
for (int n = 1; e != e + a ; ++n) {
a = a * x / n;
e += a;
}
return invert ? 1/e : e;
}
We can optimize a bit more to remove one loop iteration by initializing e with 0 instead of a, and calculating the next term at the bottom of the loop instead of the top:
double powerex(double x) {
double a = 1.0, e = 0;
int invert = x<0;
x = fabs1(x);
for (int n = 1; e != e + a ; ++n) {
e += a;
a = a * x / n;
}
return invert ? 1/e : e;
}
For values of x above one or so, you may consider to handle the integer part separately and compute powers of e by squarings. (E.g. e^9 = ((e²)²)².e takes 4 multiplies)
Indeed, the general term of the Taylor development, x^n/n! only starts to decrease after n>x (you multiply each time by x/k), so the summation takes at least x terms. On another hand, e^n can be computed in at most 2lg(n) multiplies, which is more efficient and more accurate.
So I would advise
to take the fractional part of x and use Taylor,
when the integer part is positive, multiply by e raised to that power,
when the integer part is zero, you are done,
when the integer part is negative, divide by e raised to that power.
You can even spare more by considering quarters: in the worst case (x=1), Taylor requires 18 terms before the last one becomes negligible. If you consider subtracting from x the immediately inferior multiple of 1/4 (and compensate multiplying by precomputed powers of e), the number of terms drops to 12.
E.g. e^0.8 = e^(3/4+0.05) = 2.1170000166126747 . e^0.05

Mandelbrot using Pthreads, stepping through array

I am working on a project to rewrite a sequential C-code algorithm for creating a mandelbrot set into a parallel one using pthreads. I've gone up against a wall so to speak, as my version simply outputs a more or less black picture (and nothing to what the original program results into), and I can't really see where I'm going wrong. Simply put, I could use a second pair of eyes on this one.
Here is the sequential code snippet that matters:
void mandelbrot(float width, float height, unsigned int *pixmap)
{
int i, j;
float xmin = -1.6f;
float xmax = 1.6f;
float ymin = -1.6f;
float ymax = 1.6f;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
float b = xmin + j * (xmax - xmin) / width;
float a = ymin + i * (ymax - ymin) / height;
float sx = 0.0f;
float sy = 0.0f;
int ii = 0;
while (sx + sy <= 64.0f) {
float xn = sx * sx - sy * sy + b;
float yn = 2 * sx * sy + a;
sx = xn;
sy = yn;
ii++;
if (ii == 1500) {
break;
}
}
if (ii == 1500) {
pixmap[j+i*(int)width] = 0;
}
else {
int c = (int)((ii / 32.0f) * 256.0f);
pixmap[j + i *(int)width] = pal[c%256];
}
}
}
}
Here is my sequential version of the code:
void* Mandel(void* threadId) {
int x = *(int*)threadId;
float xmin = -1.6f;
float xmax = 1.6f;
float ymin = -1.6f;
float ymax = 1.6f;
float b = xmin + x * (xmax - xmin) / WIDTH;
for (int y = 0; y < 1024; y++)
{
float a = ymin + y * (ymax - ymin) / WIDTH;
float sx = 0.0f;
float sy = 0.0f;
int ii = 0;
while (sx + sy <= 64.0f) {
float xn = sx * sx - sy * sy + b;
float yn = 2 * sx * sy + a;
sx = xn;
sy = yn;
ii++;
if (ii == 1500) {
break;
}
}
if (ii == 1500) {
pixmap[x+y*(int)WIDTH] = 0;
}
else {
int c = (int)((ii / 32.0f) * 256.0f);
pixmap[x + y *(int)WIDTH] = pal[c%256];
}
}
}
Explanation of my thought process:
I create 1024 threads in the main function, and then call on the function above with each thread. They're supposed to a column each (since the x is a constant between 0 and 1023, while the y value changes from 0 to 1023 within the function). As you can see, most of the mathematical meat in the function itself is the same in both the sequential and my parallel versions of the code. Because of this, I think the problem comes from how I'm stepping through the array, but I cannot see the problem with my own eyes. Regardless, the value that ii eventually receives is used to calculate c, which in turn is used to decide the color value that's to be saved in the corresponding position in pixmap. (pal is basically just a large array filled with color values).
This function is the only piece of the code that I've actually touched to any major degree. The only difference in the main function is that I've created threads in it with the instruction to carry out the function Mandel.
I assume that anyone willing to help will want more information, and please let me know of any improvements to this post in case I have posted too little information.
I found an answer shortly after posting this code. Silly me.
Anywho, the problem wasn't stepping through the array within the function, but actually how I created the threads.
This is how it looked in main when the problem occurred:
for(int k = 0; k < 1024; k++) {
pthread_create(&threads[k], NULL, (void*)Mandel, (void*) k);
}
The problem with doing it this way was that the loop continued, changing the value of k for the next thread, meaning that the last thread that we just created suddenly got an erronous value for k. This was solved by using an int array which saves the values of k as we go through each k value and create each thread, like shown below:
int id[1024];
for(int k = 0; k < 1024; k++) {
pthread_create(&threads[k], NULL, (void*)Mandel, (void*)(id+k));
}
I would like to point towards the post in the following link, for helping me answer this problem:
Pass integer value through pthread_create
Just goes to show that you can often find the answers you're looking for if you search long enough, for the most of the time.

For loop with unsigned int

I have a logical problem in my code, maybe it is caused by overflowing but I can't solve this on my own, so I would be thankful if anyone can help me.
In the following piece of code, I have implemented the function taylor_log(), which can count "n" iterations of taylor polynomial. In the void function I am looking for number of iterations (*limit) which is enough to count a logarithm with desired accuracy compared to log function from .
The thing is that sometimes UINT_MAX is not enough iterations to get the desired accuracy and at this point I want to let the user know that the number of needed iterations is higher than UINT_MAX. But my code don't work, for example for x = 1e+280, eps = 623. It just counts, counts and never give result.
TaylorPolynomial
double taylor_log(double x, unsigned int n){
double f_sum = 1.0;
double sum = 0.0;
for (unsigned int i = 1; i <= n; i++)
{
f_sum *= (x - 1) / x;
sum += f_sum / i;
}
return sum;
}
void guessIt(double x, double eps, unsigned int *limit){
*limit = 10;
double real_log = log(x);
double t_log = taylor_log(x, *limit);
while(myabs(real_log - t_log) > eps)
{
if (*limit == UINT_MAX)
{
*limit = 0;
break;
}
if (*limit >= UINT_MAX/2)
{
*limit = UINT_MAX;
t_log = taylor_log(x, *limit);
}
else
{
*limit = (*limit) *2;
t_log = taylor_log(x, *limit);
}
}
}
EDIT: Ok guys, thanks for your reactions so far. I have changed my code to this:
if (*limit == UINT_MAX-1)
{
*limit = 0;
break;
}
if (*limit >= UINT_MAX/2)
{
*limit = UINT_MAX-1;
t_log = taylor_log(x, *limit);
}
but it still doesn't work correctly, I have set printf to the beggining of taylor_log() function to see the value of "n" and its (..., 671088640, 1342177280, 2684354560, 5, 4, 3, 2, 2, 1, 2013265920, ...). Don't understand it..
This code below assigns the limit to UINT_MAX
if (*limit >= UINT_MAX/2)
{
*limit = UINT_MAX;
t_log = taylor_log(x, *limit);
}
And your for loop is defined like this:
for (unsigned int i = 1; i <= n; i++)
i will ALWAYS be less than or equal to UINT_MAX because there is never going to be a value of i that is greater than UINT_MAX. Because that's the largest value i could ever be. So there is certainly overflow and your loop exit condition is never met. i rolls over to zero and the process repeats indefinitely.
You should change your loop condition to i < n or change your limit to UINT_MAX - 1.
[Edit]
OP coded correctly but must insure a limited range (0.5 < x < 2.0 ?)
Below is a code version that self determines when to stop. Iteration count goes high near x near 0.5 and 2.0. The iteration count needed goes into the millions. Such the alternative coded far below.
double taylor_logA(double x) {
double f_sum = 1.0;
double sum = 0.0;
for (unsigned int i = 1; ; i++) {
f_sum *= (x - 1) / x;
double sum_before = sum;
sum += f_sum / i;
if (sum_before == sum) {
printf("%d\n", i);
break;
}
}
return sum;
}
Wrongalternative implementation of the series: Ref
Sample alternative - it converges faster.
double taylor_log2(double x, unsigned int n) {
double f_sum = 1.0;
double sum = 0.0;
for (unsigned int i = 1; i <= n; i++) {
f_sum *= (x - 1) / 1; // / 1 (or remove)
if (i & 1) sum += f_sum / i;
else sum -= f_sum / i; // subtract even terms
}
return sum;
}
A reasonable number of terms will converge as needed.
Alternatively, continue until terms are too small (maybe 50 or so)
double taylor_log3(double x) {
double f_sum = 1.0;
double sum = 0.0;
for (unsigned int i = 1; ; i++) {
double sum_before = sum;
f_sum *= x - 1;
if (i & 1) sum += f_sum / i;
else sum -= f_sum / i;
if (sum_before == sum) {
printf("%d\n", i);
break;
}
}
return sum;
}
Other improvements possible. example see More efficient series
First, using std::numeric_limits<unsigned int>::max() will make your code more c++-ish than c-ish. Second, you can use the integral type unsigned long long and std::numeric_limits<unsigned long long>::max() for the limit, which is pretty mush the limit for an integral type. If you want a higher limit, you may use long double. floating points also allows you to use infinity with std::numeric_limits<double>::infinity() note that infinity work with double, float and long double.
If neither of these types provide you the precision you need, look at boost::multiprecision
First of all, the Taylor series for the logarithm function only converges for values of 0 < x < 2, so it's quite possible that the eps precision is never hit.
Secondly, are you sure that it loops forever, instead of hitting the *limit >= UINT_MAX/2 after a very long time?
OP is using the series well outside its usable range of 0.5 x < 2.0 with calls like taylor_log(1e280, n)
Even within the range, x values near the limits of 0.5 and 2.0 converge very slowly needing millions+ of iterations. A precise log() will not result. Best to use the 2x range about 1.0.
Create a wrapper function to call the original function in its sweet range of sqrt(2)/2 < x < sqrt(2). Converges, worst case, with about 40 iterations.
#define SQRT_0_5 0.70710678118654752440084436210485
#define LN2 0.69314718055994530941723212145818
// Valid over the range (0...DBL_MAX]
double taylor_logB(double x, unsigned int n) {
int expo;
double signif = frexp(x, &expo);
if (signif < SQRT_0_5) {
signif *= 2;
expo--;
}
double y = taylor_log(signif,n);
y += expo*LN2;
return y;
}

How to implement natural logarithm with continued fraction in C?

Here I have a little problem. Create something from this formula:
This is what I have, but it doesn't work. Franky, I really don't understand how it should work.. I tried to code it with some bad instructions. N is number of iteration and parts of fraction. I think it leads somehow to recursion but don't know how.
Thanks for any help.
double contFragLog(double z, int n)
{
double cf = 2 * z;
double a, b;
for(int i = n; i >= 1; i--)
{
a = sq(i - 2) * sq(z);
b = i + i - 2;
cf = a / (b - cf);
}
return (1 + cf) / (1 - cf);
}
The central loop is messed. Reworked. Recursion not needed either. Just compute the deepest term first and work your way out.
double contFragLog(double z, int n) {
double zz = z*z;
double cf = 1.0; // Important this is not 0
for (int i = n; i >= 1; i--) {
cf = (2*i -1) - i*i*zz/cf;
}
return 2*z/cf;
}
void testln(double z) {
double y = log((1+z)/(1-z));
double y2 = contFragLog(z, 8);
printf("%e %e %e\n", z, y, y2);
}
int main() {
testln(0.2);
testln(0.5);
testln(0.8);
return 0;
}
Output
2.000000e-01 4.054651e-01 4.054651e-01
5.000000e-01 1.098612e+00 1.098612e+00
8.000000e-01 2.197225e+00 2.196987e+00
[Edit]
As prompted by #MicroVirus, I found double cf = 1.88*n - 0.95; to work better than double cf = 1.0;. As more terms are used, the value used makes less difference, yet a good initial cf requires fewer terms for a good answer, especially for |z| near 0.5. More work could be done here as I studied 0 < z <= 0.5. #MicroVirus suggestion of 2*n+1 may be close to my suggestion due to an off-by-one of what n is.
This is based on reverse computing and noting the value of CF[n] as n increased. I was surprised the "seed" value did not appear to be some nice integer equation.
Here's a solution to the problem that does use recursion (if anyone is interested):
#include <math.h>
#include <stdio.h>
/* `i` is the iteration of the recursion and `n` is
just for testing when we should end. 'zz' is z^2 */
double recursion (double zz, int i, int n) {
if (!n)
return 1;
return 2 * i - 1 - i * i * zz / recursion (zz, i + 1, --n);
}
double contFragLog (double z, int n) {
return 2 * z / recursion (z * z, 1, n);
}
void testln(double z) {
double y = log((1+z)/(1-z));
double y2 = contFragLog(z, 8);
printf("%e %e %e\n", z, y, y2);
}
int main() {
testln(0.2);
testln(0.5);
testln(0.8);
return 0;
}
The output is identical to the solution above:
2.000000e-01 4.054651e-01 4.054651e-01
5.000000e-01 1.098612e+00 1.098612e+00
8.000000e-01 2.197225e+00 2.196987e+00

Resources