21

I just started reading Hacker's Delight and it defines abs(-231) as -231. Why is that?

I tried printf("%x", abs(0x80000000)) on a few different systems and I get back 0x80000000 on all of them.

sigjuice
  • 28,661
  • 12
  • 68
  • 93

9 Answers9

43

Actually, in C, the behavior is undefined. From the C99 standard, §7.20.6.1/2:

The abs, labs, and llabs functions compute the absolute value of an integer j. If the result cannot be represented, the behavior is undefined.

and its footnote:

The absolute value of the most negative number cannot be represented in two’s complement.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 4
    Absolutely +1 for pointing out the undefinedness of the whole thing instead of going to lengths explaining what a certain platform just happens to make off it. – DevSolar Mar 30 '10 at 06:43
14

For a 32bit datatype there is no expression of +2^31, because the biggest number is 2^31-1 ... read more about the two's complement ...

tanascius
  • 53,078
  • 22
  • 114
  • 136
  • Thanks. I get it. But, did you mean to say "there is no expression of 2^31"? – sigjuice Mar 29 '10 at 15:45
  • 4
    @sigjuice: The range of a 32bit datatype is -2^31 to 2^31-1 ... so, yes, there is no expression for 2^31 - it will result in an overflow – tanascius Mar 29 '10 at 15:47
10

Because integers are stored in memory as a two's complement binary number, the positive version of the minimum value overflows back to negative.

That is to say (in .NET, but still applies):

int.MaxValue + 1 == int.MinValue  // Due to overflow.

And

Math.Abs((long)int.MinValue) == (long)int.MaxValue + 1
John Gietzen
  • 48,783
  • 32
  • 145
  • 190
8

Obviously, mathematically, |−231| is 231. If we have 32 bits to represent integers, we can represent at most 232 numbers. If we want a representation that is symmetric about 0, we have a few decisions to make.

For the following, as in your question, I am assuming 32-bit wide numbers. At least one bit pattern must be used for 0. So that leaves us with 232−1 or less bit patterns for the rest of the numbers. This number is odd, so we can either have a representation that's not exactly symmetric about zero, or have one number be represented with two different representations.

  • If we use sign-magnitude representation, the most significant bit represents the sign of a number, and the rest of the bits represent the magnitude of the number. In this scheme, 0x80000000 is "negative zero" (i.e., zero), and 0x00000000 is "positive zero" or regular zero. In this scheme, the most positive number is 0x7fffffff (2147483647) and the most negative number is 0xffffffff (−2147483647). This scheme has the advantage that it is easy for us to "decode", and that it is symmetric. This scheme has a disadvantage in that calculating a + b when a and b are of different signs is a special case, and has to be dealt with specially.
  • If we use a ones' complement representation, the most significant bit still represents the sign. Positive numbers have that bit as 0, and the rest of the bits make up the magnitude of the number. For negative numbers, you just invert the bits from the corresponding positive number's representation (take a complement with a long series of ones—hence the name ones' complement). In this scheme, the maximum positive number is still 0x7fffffff (2147483647), and the maximum negative number is 0x80000000 (−2147483647). There are again two representations of 0: positive zero is 0x00000000 and negative zero is 0xffffffff. This scheme also has issues with calculations involving negative numbers.
  • If we use a two's complement scheme, the negative numbers are obtained by taking ones' complement representation and adding 1 to it. In this scheme, there is only one 0, namely 0x00000000. The most positive number is 0x7fffffff (2147483647) and the most negative number is 0x80000000 (−2147483648). There is an asymmetry in this representation. The advantage of this scheme is that one doesn't have to deal with special cases for negative number. The representation takes care of giving you the right answer as long as the result doesn't overflow. For this reason, most of the current hardware represents integers in this representation.

In two's complement representation, there is no way to represent 231. In fact, if you look at your compiler's limits.h or equivalent file, you might see a definition for INT_MIN in such a way:

#define INT_MIN (-2147483647 - 1)

This done rather than

#define INT_MIN -2147483648

because 2147483648 is too large to fit in an int in a 32-bit two's complement representation. By the time the unary minus operator "gets" the number to operate on, it is too late: overflow has already occurred and you can't fix it.

So, to answer your original question, the absolute value of the most negative number in a two's complement representation cannot be represented in that encoding. Also, from the above, to get from a negative value to a positive value in two's complement representation, you take its ones' complement and then add 1. So, for 0x80000000:

1000 0000 0000 0000 0000 0000 0000 0000   original number
0111 1111 1111 1111 1111 1111 1111 1111   ones' complement
1000 0000 0000 0000 0000 0000 0000 0000   + 1

you get the original number back.

Alok Singhal
  • 93,253
  • 21
  • 125
  • 158
3

This goes back to how numbers are stored.

Negative numbers are stored using two's complement. The algorithm goes like ...

Flip all the bits, then add 1.

Using eight bit numbers for examples ...

+0 = -0

00000000 -> 11111111, 11111111 + 1 = 100000000

(but due to limitation of bits, this becomes 00000000).

AND...

-128 [aka -(2^7)] equals -(-128)

10000000 -> 01111111, 01111111 + 1 = 10000000

Hope this helps.

Sparky
  • 13,505
  • 4
  • 26
  • 27
3

The representation of a two's complement number has the most significant bit as a negative number. 0x80000000 is 1 followed by 31 zeroes, the first 1 represents -2^31 not 2^31. Therefore there is no way to represent 2^31 as the highest positive number is 0x7FFFFFFF, which is 0 followed by 31 ones, which equals 2^31-1.

abs(0x80000000) is therefore undefined in the two's complement since it is too large, due to this the machine just gives up and gives you 0x80000000 again. Typically at least.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Guvante
  • 18,775
  • 1
  • 33
  • 64
1

I think the way abs works is to first check the sign bit of the number. If its clear do nothing as the number is already +ve else return the 2's complement of the number. In your case the number is -ve and we need to find its 2's complement. But 2's complement of 0x80000000 happens to be 0x80000000 itself.

codaddict
  • 445,704
  • 82
  • 492
  • 529
  • 1
    That check is very unlikely to happen. Such a check would be utterly useless – the result is *the same* – all the while consuming extra time for *every call*. Not a very good trade-off between cost and benefits. – Konrad Rudolph Mar 29 '10 at 15:45
  • 1
    Hmm, you mean the check if the number is already positive? But if you took the 2's complement of a positive number, you would get the negative, not the absolute value. – Jay Mar 29 '10 at 16:42
1

0x8000.. is stored as 10000.... (binary). This is known as twos complement, which means that the highest bit (the one at the left) is used to store the sign of the value and negative values are stored with negative binary - 1. The abs() function now checks the signbit, sees that it is set and computes the positive value.

  • To get the positive value it first negates all bits in the variable, resulting in 01111...
  • Then adds 1, which again results in 1000... the 0x8000... we started with

Now this is a negative number again which we didn't want, the reason is a overflow, try the number 0x9000... which is 10010...

  • negating the bits results in 01101... adding one results in 01110...
  • which is 0xE000...a positive number

With this number the overflow is stopped by the 0 bit on the right

josefx
  • 15,506
  • 6
  • 38
  • 63
0

because it uses the neg instruction to perform this operation.

In the Art of Assembly language programming book they said like this.

If the operand is zero, its sign does not change, although this clears the carry flag. Negating any other value sets the carry flag. Negating a byte containing -128, a word containing -32,768, or a double word containing -2,147,483,648 does not change the operand, but will set the overflow flag. Neg always updates the A, S, P, and Z flags as though you were using the sub instruction

source :http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_6/CH06-2.html#HEADING2-313 So it will set the overflow flag and be silently.That's the reason.

sandun dhammika
  • 881
  • 3
  • 12
  • 31