Thread: weird behaviour with abs()

  1. #1
    Registered User
    Join Date
    May 2008
    Posts
    14

    weird behaviour with abs()

    Code:
    #include <stdio.h>
    
    int main() {
    
    printf("%d %d\n", abs(50.0), (int) 50.0);
    }

    Output:
    0 50


    Does anyone have any idea why abs(50.) returns 0? I am aware of the fabs() function, i am just wondering about how C casts 50.0 before it passes it to abs().

    Thanks

  2. #2
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Because 50.0 is not an integer, it's a double. In C, unless you add a cast, the following all count as:

    50 -- an integer.
    50.0 -- a double.
    50.0f -- a float.

    If you turned your compiler warnings on it would probably inform you that abs takes an integer but arg 1 is a double.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  3. #3
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by crazyinlove View Post
    Does anyone have any idea why abs(50.) returns 0? I am aware of the fabs() function, i am just wondering about how C casts 50.0 before it passes it to abs().
    If you had included math.h like you're supposed to, the compiler would have done the conversion automatically. But since you didn't, the compiler passes it as an double. abs() then tries to interpret this argument as an integer, and does the wrong thing.

    Your code would work as expected if you included math.h
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  4. #4
    Registered User
    Join Date
    May 2008
    Posts
    14
    >If you had included math.h like you're supposed to, the compiler would have done the conversion automatically. But since you didn't, the compiler passes it as an double. abs() then tries to interpret this argument as an integer, and does the wrong thing.

    I think the compiler converts implicitly 50.0 to 50, and then calls the function. I included math.h and I got the same result...

    >If you turned your compiler warnings on it would probably inform you that abs takes an integer but arg 1 is a double.
    The problem is that the compiler has to cast the double before sending it to 'int abs(int)', so where does that 0 come from?

  5. #5
    Registered User
    Join Date
    May 2008
    Posts
    14
    abs() is not in math.h, it's in stdlib

  6. #6
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by crazyinlove View Post
    abs() is not in math.h, it's in stdlib
    Yep. Brewbuck was probably thinking of fabsf() or some such.

    Vis, the warnings -- actually my compiler does not beef about it, and it appears to do the conversion.

    However, I have noticed other situations where it will do this (if you submit a double instead of an int, it just renders the double as 0).

    Sometimes they are converted.

    The lesson is: make sure you cast appropriately when needed. Eg, try:

    Code:
    abs((int)50.0);
    If that is still a problem, it is not hard (5-10 lines) to write your own custom "absolute" function which will take whatever kind of number you design it to take and return the positive value (as whatever type you want returned).
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  7. #7
    Registered User
    Join Date
    May 2008
    Posts
    14
    But I know how to fix this little bug, I just can't understand why it's happening...

  8. #8
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300


    Here's an analogy: I can use the compiler, I just can't figure out how it works.

    Actually, neither statement is 100% true, but more to the point: you don't have to know how to make a hammer in order to use it properly.

    So someone knows the exact answer, but we have the important parts already:
    - there is a specific rule we have violated (submitting a double to a function expecting an int)
    - when we break rules, one thing that can happen is undefined behavior:

    Undefined behavior - Wikipedia, the free encyclopedia

    In computer science, undefined behavior is a feature of some programming languages — most famously C. In these languages, to simplify the specification and allow some flexibility in implementation, the specification leaves the results of certain operations specifically undefined.
    AFAIK, the ISO C99 standard does not cover compiler warnings at all. There are also many potential "error" situations it does not cover. So, for example, presuming your compiler is ISO C99 compliant, it still does not say what the compiler should do if someone submits a floating point value, or a char pointer, or a white rabbit in place of what it does specify -- an int. My compiler converts the double to an int. It will probably not convert a string, eg:

    abs("fifty");

    WHY???!!?? Everyone knows what fifty is! I'd be impressed if it even returned 0, tho.

    Your compiler probably just replaces the double with 0. You can always write those responsible and ask them why the command does "weird" things when you do not use it properly...
    Last edited by MK27; 02-18-2010 at 09:12 PM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  9. #9
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by crazyinlove View Post
    abs() is not in math.h, it's in stdlib
    You're right. At any rate, if you include the proper header, the code works as expected.

    The REASON is that without the header, the compiler has no idea what type of argument the abs() function is expecting. So it assumes that the expected type is the type which you are actually using (in this case, double). So it passes the argument as a double. But since abs() is actually expecting an integer to be there, it accesses the argument as if it was an integer. Since the bit pattern which stands for a double value of 50.0 is nothing like the bit pattern which stands for integer 50, abs() returns the wrong result.

    Including the header solves the problem, because the compiler now has the knowledge that abs() expects an integer. It sees that you are trying to pass a double, and "helpfully" converts it to an integer automatically for you. There is nothing strange or undefined about this behavior. It is valid C, although some compilers may warn you when you do this.

    By adding an explicit (int), you are forcing the compiler to cast the value, and pass it as an int, which is what abs() expects. So if you manually cast it yourself, it works. The point is, by including the correct header, you are enabling the compiler to AUTOMATICALLY cast it for you.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  10. #10
    Registered User
    Join Date
    May 2008
    Posts
    14
    ncluding the header solves the problem, because the compiler now has the knowledge that abs() expects an integer. It sees that you are trying to pass a double, and "helpfully" converts it to an integer automatically for you. There is nothing strange or undefined about this behavior. It is valid C, although some compilers may warn you when you do th
    Question: In the case that we don't include the right header, why doesn't the compiler complain that abs() was not declared before used? Since it's not complaining, and it's doing whatever comes to its mind, then the compiler is not really following the standards....

  11. #11
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Ah. This is not 100% clear to me but here is why I think it is so:

    Executables and the libraries they use are usually "dynamically linked". You have one binary version of the C library on your computer. Generally speaking, this is always loaded into RAM already. Rather than compiling in the C library or the relevant parts -- in which case all the combined software running on your desktop right now would require like ten times as much memory, 90% of it duplicated -- an executable is linked to the the library objects.

    So, functions in stdlib are present in those linked objects whether you use them or not. Those objects are in memory anyway, whether you use them or not (because something else probably is using them). But when you compile an executable without including the header*, the compiler does not know how to make proper use of those functions, and as brewbuck says it falls back on this default prototype:
    Code:
    int default();
    This is a function that will accept any number and kind of parameters and returns an int.


    *which is actual compilable c code; witness, if you do not use a compiler or program, like most people, you do not need and probably do not have any C library headers on your computer at all, but you do have the objects that they "index".
    Last edited by MK27; 02-20-2010 at 07:19 AM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  12. #12
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Quote Originally Posted by crazyinlove View Post
    Question: In the case that we don't include the right header, why doesn't the compiler complain that abs() was not declared before used? Since it's not complaining, and it's doing whatever comes to its mind, then the compiler is not really following the standards....
    The standard allows the use of an undeclared function -- the parameters are treated as I described. The compiler has the option of warning you when you do this. gcc does not warn about it, unless you specifically turn on warnings.

    gcc is a little weird in how it reports these. For instance, it will warn if you try to call printf() without include stdio.h. This is because the compiler actually has an internalized implementation of printf() which optimizes various common usage patterns, so it has built-in knowledge of what printf() takes as parameters. When you call printf() without its prototype, the argument conversion doesn't match what gcc thinks it should be, and it whines.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  13. #13
    Registered User
    Join Date
    Oct 2008
    Posts
    1,262
    I may have missed it, but I didn't see anyone explaining why this happens on low level. I think I know why (I'm not sure, haven't looked into it, just guessed it).

    See, an argument is passed on the stack. An integer is usually 32 bits, a double 64 bits. When the double is pushed it pushes two times 32 bits, so the float is split into two distinct values. The least significant bits will be meaningful, in this case, while the most significant bits aren't used yet (as 50.0 can be represented in a 32 bits floating point number) and will be set to 0.
    So basically, two integers are pushed onto the stack: the least significant 4 bytes of the double, which is some meaningless value, and then the other half, only consisting of 0s and thus having the integer value 0.
    The abs function will only use the last parameter pushed, as it only expects one parameter. This is 0. It will then be abs'ed, into 0. This value will be returned.

    I can't promise this is the reason, but this is what I expect.

  14. #14
    Registered User
    Join Date
    Jan 2010
    Posts
    412
    Quote Originally Posted by EVOEx View Post
    I can't promise this is the reason, but this is what I expect.
    I agree. And if the runtime library had declared abs() as some other calling convention than the compiler's default one (most likely cdecl) it would cause a stack corruption crash either in printf() or when returning from main() so this behaviour is implementation specific.

  15. #15
    Registered User
    Join Date
    May 2008
    Posts
    14
    OK thanks for the replies

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. StretchDIBits() - weird behaviour..
    By lobo in forum Windows Programming
    Replies: 13
    Last Post: 10-04-2010, 11:23 PM
  2. weird things with my linked list of queue
    By -EquinoX- in forum C Programming
    Replies: 3
    Last Post: 11-22-2008, 11:23 PM
  3. Replies: 16
    Last Post: 04-20-2008, 01:15 PM
  4. VC++ 2005 Express: Weird Behaviour
    By psychopath in forum Tech Board
    Replies: 2
    Last Post: 06-21-2006, 07:47 PM
  5. round and abs in c++
    By asimos in forum C++ Programming
    Replies: 6
    Last Post: 02-02-2002, 02:15 PM