-4

ex:2^0.5=1.414....&so want. I'm newbie to c so please explain simple logic if not complicated logic is also enough

  • This sounds more like a math problem than a programming problem. – dbush Jan 06 '22 at 04:43
  • 3
    "without using ..." no. That's a math question at this point. – o11c Jan 06 '22 at 04:43
  • Use `sprintf(buffer, "%f", some_double);` and lop off the whole number part. Use `strchr(buffer, '.');` to find the decimal point. – chux - Reinstate Monica Jan 06 '22 at 04:44
  • @chux-ReinstateMonica That's not what's being asked. The question is "how do we implement `pow(a, b)`" – o11c Jan 06 '22 at 04:45
  • OP, if you really want to do this, I would suggest starting by implementing `sin` using a Taylor series, since that's easier as an introduction. – o11c Jan 06 '22 at 04:48
  • The easy and straightforward way, of course, is to just call `pow(2.0, 0.5)`. But of course `pow` is in ``. The next-easiest way is to call `exp(log(2.0) * 0.5)`... but of course `log` and `exp` are in `` also. Why can you not use them? Is this a class exercise, or are you working on some limited platform without proper `` support? Implementing these functions, yourself, well, is an excellent exercise, but not easy. Jakob Stark's answer is a good start. – Steve Summit Jan 08 '22 at 01:55

1 Answers1

3

In mathematics, the step from integer exponentiation to real exponentiation is quite a big step. While integer exponentiation can be defined as repeated multiplication (e.g. x^5 = x*x*x*x*x) exponentiation with real values in the exponent is commonly defined via the natural exponential function. Your example of 2^0.5 can be rewritten as

2^0.5 = exp(0.5*ln(2))

where exp is the natural exponential function and ln is its inverse, the natural logarithm. Both these functions have commonly known power series:

exp(x) = 1 + x + x^2/2 + x^3/3 + ... 
ln(x)  = (x-1) - (x-1)^2/2 + (x-1)^3/3 - ...

The power series are infinite series, so you cannot calculate it exactly, but depending on how accurate you want your result to be, you can stop at some point in the series. The formula for the whole series can be found for example at wikipedia.

Note that the series expansion for the logarithm is only convergent for values of x between 0 and 2. In your example, the ln(2) is already at the edge of this region. A solution to this problem can be found by rewriting the expression even further. For any number x, ln(x) can be expandend in

ln(x) = ln(m*2^e) = ln(m) + e*ln(2)

Where m is the mantissa (between 1 and 2) and e is the exponent of the number x. In your example you would for example write:

ln(2) = ln(1*2^1) = ln(1) + 1*ln(2)

The value of ln(2) can be precalculated and stored as a constant, as it is always the same. The value of ln(1) can be accurately calculated using the series expansion from above (In your example this happens to be ln(1) = 0, but this is not generally the case). The mantissa and exponent can easily be extracted from a double or float type, by using their internal bit representation.

Armed with this information we can try to write our own ln, exp and ultimately pow functions:

#include <stdio.h>

/* constant to control how many terms in the series expansion we want to calculate before aborting */
const int N = 50;

float my_ln(float x) {
    const float ln2 = 0.6931471805599453;
    union {
        float f;
        uint32_t i;
    } u;

    /* get the mantissa by bit manipulation of the float binary representation */
    u.f = x;
    u.i = (u.i & 0x07ffffff) | 0x3f800000;

    /* calculate the logarithm according to its series expansion */
    float result = 0.0;
    float tmp = u.f - 1.0;
    for ( int i = 1; i < N; i++ ) {
        result += tmp/i;
        tmp *= 1.0 - u.f;
    }

    /* get the exponent */
    u.f = x;
    u.i = ((u.i >> 23) & 0xff) - 127;

    /* multiply it with ln(2) and add it to the logarithm of the mantissa */
    result += (float)u.i * ln2;
    return result;
}

float my_exp(float x) {
    float result = 0.0;
    float tmp = 1.0;
    for ( int i = 1; i < N; i++ ) {
        result += tmp;
        tmp *= x/i;
    }
    return result;
}

float my_pow(float x, float a) {
    /* multiply the logarithm of the base x with the exponent a and compute the natural exponential of this value */
    return my_exp( my_ln(x) * a);
}

int main() {
    float x = 2.0;
    float a = 0.5;
    printf("%f ^ %f == %f\n", x,a, my_pow(x,a));
}

This gives fairly good results, at least for the few examples that i tested. Note that writing such functions is difficult and error prone. For example the functions, that I provided above do not handle negative numbers correctly. NaNs, Infinities and Zeros are not handled correctly as well. While this could also be done with some more effort, one really should use the functions from the standard library, as they are

  • well tested
  • probably much faster and optimized
  • probably much more accurate in more extreme number regions (very small or large numbers)
Jakob Stark
  • 3,346
  • 6
  • 22
  • Nice answer. One nit: *The mantissa and exponent can easily be extracted from a `double` or `float` type...* **if** the raw exponent is not at its min or max value. If it is, you've got a zero, subnormal, infinity, or NaN to worry about, and those aren't quite so easy. – Steve Summit Jan 08 '22 at 01:46
  • See also [bitwise splitting the mantissa of a IEEE 754 double?](https://stackoverflow.com/questions/69207580) and [how can smallest floating point number be 2^(-126)?](https://stackoverflow.com/questions/43153699). – Steve Summit Jan 08 '22 at 01:58
  • @SteveSummit In fact, I did not even correctly handle the sign bit, but just ignored it – Jakob Stark Jan 08 '22 at 10:10