Thread: a question on the use of pow() function

  1. #1
    Registered User
    Join Date
    Oct 2013
    Posts
    17

    a question on the use of pow() function

    so i had code "test.c" like this

    Code:
    #include <stdio.h>
    #include <math.h>
    
    int main(void) {
      float f;
      f=pow(2.1, 2);
      printf("f is %f\n", f);
      return 0;
    }
    and it compiles fine

    gcc test.c

    but when i change the code slightly

    Code:
    #include <stdio.h>
    #include <math.h>
    
    int main(void) {
      float f;
      f=pow(2.1, 2) + pow(2.1, 2);
      printf("f is %f\n", f);
      return 0;
    }
    when compiled, it complains

    undefined reference to `pow'

    it seems i had to add the "-lm" flag to have the modified code compiled, what gives?

  2. #2
    Guest Sebastiani's Avatar
    Join Date
    Aug 2001
    Location
    Waterloo, Texas
    Posts
    5,708
    This is sort of a mix between language and implementation idiosyncrasies. First, remember that in C you're allowed to refer to symbols that aren't even defined in a header. So what happened here is that in the first case gcc somehow guessed that you meant double pow(double, double) from the standard math libary and linked with it, but in the second instead saw to much ambiguity (ie: did he mean int pow(double, int) from some external library?) and so the math lib was never linked against it. The moral of the story: "say what you mean" and don't rely on implicit conversions.
    Code:
    #include <cmath>
    #include <complex>
    bool euler_flip(bool value)
    {
        return std::pow
        (
            std::complex<float>(std::exp(1.0)), 
            std::complex<float>(0, 1) 
            * std::complex<float>(std::atan(1.0)
            *(1 << (value + 2)))
        ).real() < 0;
    }

  3. #3
    Registered User
    Join Date
    Mar 2009
    Posts
    344
    Pretty close, but more likely here is that GCC is able to optimize some calls to math lib functions away completely when you pass in constants. Not sure why the second case can't be optimized away like the first, but if you dump the ASM I'd guess there's no call to pow() there in the first example.

  4. #4
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Interesting...what version of gcc are you using? Are you using any other compiler flags? I'm using 4.6.3 and get no errors with either (when compiling with -Wall). Perhaps you can look at the change list for versions between yours and mine to see if there's a change or bug fix that might affect this.
    Code:
    $ gcc --version
    gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
    $ cat pow1.c
    #include <stdio.h>
    #include <math.h>
    
    
    int main(void) {
      float f;
      f=pow(2.1, 2);
      printf("f is %f\n", f);
      return 0;
    }
    $ gcc -Wall pow1.c
    $ cat pow2.c
    #include <stdio.h>
    #include <math.h>
    
    
    int main(void) {
      float f;
      f=pow(2.1, 2) + pow(2.1, 2);
      printf("f is %f\n", f);
      return 0;
    }
    $ gcc -Wall pow2.c
    $

  5. #5
    Registered User
    Join Date
    Oct 2013
    Posts
    17
    i apologize, the actual code i was compiling with has more lines to it, i thought i dump it down to simplify the question, could you try with these 2 code samples, the gcc version shows 4.4.7

    Code:
    #include <stdio.h>
     #include <math.h>
    
    int main(void) {
       float f, x=2.1;
       f=pow(x, 2);
       printf("f is %f\n", f);
       return 0;
    }

    Code:
    #include <stdio.h>
    #include <math.h>
    
    
    int main(void) {
      float f, x=2.1;
      f=pow(x, 2) + pow(x, 3);
      printf("f is %f\n", f);
      return 0;
    }
    i added the "-lm" flag as as well as the "-S' flag(also added to the 1st compile) when compiling the 2nd program, and compared the 2 ".s" files, the 2nd ".s" file has this extra line

    Code:
    call    pow
    among others, is that what you guys are talking about? could you elaborate a bit more on the difference of gcc's optimization vs. linking against a standard library?
    Last edited by brassbin; 10-16-2013 at 12:19 PM.

  6. #6
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    EDIT: Wrote this before your latest post...trying your new samples now.

    KCfromNC seems to have the right of it. GCC sees an expression for which it can evaluate the value at compile time. This is a type of constant folding. Your version of GCC probably took a simpler approach to constant folding, saying "any single function call that I can pre-compute I will do so, but if there's any compound expression I wont fold it". Later versions probably improved that.

    As a sort of crude test, I compiled different versions to assembly with different optimization levels. The only significant difference between pow1 and pow2 at any optimization level was the pre-computed value of f that was passed to printf. None had a call to pow(). That proves that, at least for my version of GCC, the expression is completely evaluated at compile time, and the constant value is used in it's place at run time, even when optimizations are disabled.

  7. #7
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    With the new code, I get the same results as you, the second example needs to be linked to the math library. Looks like it's not a version issue.

    Yes, the presence of call pow in the assembly is proof of that, that is the compiler telling the linker that it actually needs to call the pow() function.

    While the pow() function is part of the standard C specification, it is not part of the "standard library", at least on Linux implementations. I believe this is a holdover from days long past when a FPU (floating point unit) was not integrated into the CPU and thus, depending on which FPU you had on your system (if any -- some lower end systems used to omit them, doing FP calcs with the regular CPU -- it was slower but cheaper), you would need to generate different instructions for computing pow() or other floating point operations. That is why FP functions were in a separate "math" library.

    Now, as to GCC's optimization, it boils down to constant folding. You are compiling with the default optimization level (-O0 which is no optimizations). That level does not constant-fold the more complex expression in pow2.c. Some quick scripting to produce assembly code for pow1 and pow2 at each optimization level:
    Code:
    $ for i in {1..2};
    do
        for opt in {0..3};
        do
            gcc -Wall -S -O${opt} -o pow${i}.${opt}.s pow${i}.c;
        done;
    done
    That produces files like pow1.0.s for level 0 optimization, pow1.1.s for level 1 optimization, etc. Now, using diff to compare pow1 and pow2 assembly code at equal optimization levels:
    Code:
    $ for opt in {0..3};
    do
        echo "Optimization level ${opt}";
        diff pow{1,2}.${opt}.s;
        echo;
    done
    Optimization level 0
    1c1
    <     .file    "pow1.c"
    ---
    >     .file    "pow2.c"
    3c3
    < .LC1:
    ---
    > .LC2:
    16c16
    <     subq    $16, %rsp
    ---
    >     subq    $32, %rsp
    21a22,31
    >     movsd    %xmm0, -24(%rbp)
    >     movss    -8(%rbp), %xmm0
    >     cvtps2pd    %xmm0, %xmm0
    >     movsd    .LC1(%rip), %xmm1
    >     call    pow
    >     movsd    %xmm0, -32(%rbp)
    >     movq    -32(%rbp), %rax
    >     movq    %rax, -32(%rbp)
    >     movsd    -32(%rbp), %xmm0
    >     addsd    -24(%rbp), %xmm0
    27c37
    <     movl    $.LC1, %eax
    ---
    >     movl    $.LC2, %eax
    37a48,52
    >     .section    .rodata
    >     .align 8
    > .LC1:
    >     .long    0
    >     .long    1074266112
    
    
    Optimization level 1
    1c1
    <     .file    "pow1.c"
    ---
    >     .file    "pow2.c"
    28,29c28,29
    <     .long    3758096384
    <     .long    1074897878
    ---
    >     .long    536870912
    >     .long    1076582285
    
    
    Optimization level 2
    1c1
    <     .file    "pow1.c"
    ---
    >     .file    "pow2.c"
    29,30c29,30
    <     .long    3758096384
    <     .long    1074897878
    ---
    >     .long    536870912
    >     .long    1076582285
    
    
    Optimization level 3
    1c1
    <     .file    "pow1.c"
    ---
    >     .file    "pow2.c"
    29,30c29,30
    <     .long    3758096384
    <     .long    1074897878
    ---
    >     .long    536870912
    >     .long    1076582285
    That bit I highlighted in red is the key difference between pow1 and pow2 at optimization level 0. It is all the extra code for actually computing the expression run-time instead of pre-computing it at compile time. Also note that it actually calls pow(). In the rest of the optimization levels, the difference between pow1 and pow2 boils down to using different pre-computed values.

    Note, I changed the exponent in the second call from 3 to 2 and the call to pow() went away, though pow2 still required more code:
    Code:
    Optimization level 0
    1c1
    <     .file    "pow1.c"
    ---
    >     .file    "pow2.c"
    21a22,26
    >     movapd    %xmm0, %xmm1
    >     movss    -8(%rbp), %xmm0
    >     cvtps2pd    %xmm0, %xmm0
    >     mulsd    %xmm0, %xmm0
    >     addsd    %xmm1, %xmm0
    It looks like GCC converts calls to pow() with an exponent of 2, to a simple floating point multiplication of a number by itself. You can imagine that for higher powers, using a series of multiplies might not be so efficient. The pow() implementation probably uses some special tricks.
    Last edited by anduril462; 10-16-2013 at 01:12 PM. Reason: fix coloring

  8. #8
    Registered User
    Join Date
    Oct 2013
    Posts
    17
    Thank you all very much for your help!
    anduril462, just want to say this, i'm telling myself as i type up this reply, that i shouldn't expect my future questions to be answered with this level of care and detail, but..at the same time i'm so glad i found this forum

  9. #9
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by anduril462 View Post
    While the pow() function is part of the standard C specification, it is not part of the "standard library", at least on Linux implementations.
    It depends on what you mean by "standard library". pow() is a part of the C standard library, as specified in the C standard (for hosted implementations, at least).

    There is no requirement, however, that the C standard library be implemented as a single library. On most unix (including linux) implementations, the C standard library is represented using a number of library files. Functions in the math library, including pow(), are among those that are supplied in a library that is not linked in by default in a standard build (hence the need to pass -lm as a link parameter).

    Quote Originally Posted by anduril462 View Post
    I believe this is a holdover from days long past when a FPU (floating point unit) was not integrated into the CPU and thus, depending on which FPU you had on your system (if any -- some lower end systems used to omit them, doing FP calcs with the regular CPU -- it was slower but cheaper), you would need to generate different instructions for computing pow() or other floating point operations. That is why FP functions were in a separate "math" library.
    You're partly correct, but it actually goes back further than that. The original versions of C did not include support of floating point at all.

    C was used to implement (or reimplement) early versions of unix, and the fundamental workings of system programs (operating system, compilers, etc) do not require floating point operations at all (a compiler does not need to do a floating point operation to output a floating point instruction to an object file, an operating system does not need to do a floating point operation just because a program it hosts does).

    When floating point was introduced, developers building compilers and operating systems wanted an explicit assurance that they could build their code without accidentally locking in dependence on a FPU - because even linking against (early implementations of) any math library would cause a program to check for presence of a FPU (or a coprocessor) and terminate if one wasn't present, even if the program didn't otherwise use floating pointing point. Not exactly a desirable behaviour for an operating system, for example.

    Support of floating point in C was actually a bit of an afterthought. The way it was originally done (a large proportion of operation on floating point variables other than double involves conversions to or from double) also clearly shows that whoever introduced floating point support in C did not understand fundamentals of numerical analysis. That's also probably the reason that a lot of basic operations on floating point (raising to a power, modulo, etc) are done using functions rather than operators.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. function question
    By CIO in forum C Programming
    Replies: 6
    Last Post: 04-02-2009, 10:30 AM
  2. Function Question
    By Massaker in forum C Programming
    Replies: 3
    Last Post: 09-10-2005, 01:59 PM
  3. function question
    By joerules22 in forum C++ Programming
    Replies: 3
    Last Post: 08-31-2005, 04:17 PM
  4. Question on function syntax and calling function
    By cbrman in forum C Programming
    Replies: 10
    Last Post: 10-05-2003, 05:32 PM
  5. question on gets() function
    By Unregistered in forum C++ Programming
    Replies: 11
    Last Post: 03-07-2002, 03:47 PM