2

I'm working in python3.6 on linux and came across a pretty obvious failure of the abs() function. My variable x ended up as a very large negative number (possibly -inf), but the absolute value abs() function still returned a negative number, which shouldn't be possible. I put in a quick fix for my code by just adding 0.1 to the input of abs() but.... am I misunderstanding how abs() should be used?

$> x
-9223372036854775808

$> abs(x)
-9223372036854775808

$> np.abs(x)
-9223372036854775808

$> abs(x+.1)
9.223372036854776e+18

$> np.abs(x+.1)
9.223372036854776e+18

EDIT: Solved below, but it boils down to x being a numpy.int64 and not just int, unbeknownst to me.

11
  • 3
    I can't reproduce this in Python 3.6.3 on MacOS High Sierra.
    – Barmar
    Commented Jan 8, 2021 at 17:37
  • 1
    I also tried to reproduce it using x= -9223372036854775808 then abs(x) and it produced a positive result
    – giraffesyo
    Commented Jan 8, 2021 at 17:37
  • 4
    Please specify the shell you're using and the Python version. This does not reproduce with Python 3.8 or 2.7, either command-line execution of a program or IDLE shell.
    – Prune
    Commented Jan 8, 2021 at 17:38
  • Works fine for me with python 3.8.5 and ipython in Ubuntu 20.04.
    – daniboy000
    Commented Jan 8, 2021 at 17:39
  • print(x, type(x)) shows what? Commented Jan 8, 2021 at 17:41

1 Answer 1

8

You didn't think to mention it (I inferred it from your tests with np.abs), but it's important that x is a numpy.int64 (or equivalent signed 64 bit type). That specific value, in two's complement, has no positive equivalent, so abs just produces the same value again (it could be made to raise an exception, but numpy stuck with the low level C behavior where it returns the original value in this case).

Convert it to a true Python int first, e.g. abs(int(x)) and it will work.


Explanation of why it works this way:

The bit pattern of -9223372036854775808 is 0x8000_0000_0000_0000 (highest bit only is set, underscores for readability). Two's complement negation is handled algorithmically by flipping all the bits and then adding one, with carry, so the conversion changes 0x8000_0000_0000_0000 to 0x7fff_ffff_ffff_ffff (all bits flipped), then adds 1, which carries the whole length of the field (since every bit but the high bit is set), producing 0x8000_0000_0000_0000 again. That same bit pattern does actually correspond to the bit pattern an unsigned 64 bit quantity equal to 9223372036854775808 would have, but given it's interpreted as signed, it continues to be interpreted as the most negative value, not one higher than the most positive value int64 value (which can't be represented as an int64).

5
  • This does feel like a python bug to me, not very Zen Commented Jan 8, 2021 at 17:47
  • 1
    @Chris_Rands: It's not Python, it's numpy; Python abs trusts the __abs__ method of the type to return the correct value (it can't know it's really an integer and that the return value is logically wrong). And numpy is rather more "bare metal, performance at all costs" than core Python (which isn't that, at all); checking literally every mathematical operation for overflow in a case like this, where 2**64 - 1 values don't need it, and one maybe does (heavy number crunching code sometimes wants silent overflow), slows things down for minimal benefit (if any). Commented Jan 8, 2021 at 17:51
  • 3
    @paradox: Not one of the downvoters, but I get it; the OP neglected to tell us that x was a numpy type. While my psychic debugging powers are awesome, and in this case, I was pretty much 100% sure they were on target (I'd have restricted myself to a comment if I wasn't), questions that omit critical details (like the libraries used and the definitions of variables) aren't providing a minimal reproducible example, and are arguably "bad". Commented Jan 8, 2021 at 17:53
  • @ShadowRanger Yes. I agree with you that OP should have done better to give us the right context.
    – paradox
    Commented Jan 8, 2021 at 17:57
  • Thanks so much for the answers! Sorry, I didn't even realize x was a numpy.int64 and not just an int. I'll edit the original post to reflect this
    – jk_sri
    Commented Jan 8, 2021 at 18:29

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.