All the existing answers are correct. I just want to add on Windows 10 I got a different result, namely 9223372036854775808.
Steps to reproduce:
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.13.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import numpy as np
In [2]: a = np.zeros(1,np.uint64)
In [3]: a
Out[3]: array([0], dtype=uint64)
In [4]: a[0] -= 1
In [5]: a
Out[5]: array([18446744073709551615], dtype=uint64)
In [6]: a[0] - 1
Out[6]: 1.8446744073709552e+19
In [7]: a[0] - 1 == 2**64
Out[7]: True
In [8]: a[0] -= 1
<ipython-input-8-9ab639258820>:1: RuntimeWarning: invalid value encountered in cast
a[0] -= 1
In [9]: a
Out[9]: array([9223372036854775808], dtype=uint64)
In [10]: f'{a[0]:b}'
Out[10]: '1000000000000000000000000000000000000000000000000000000000000000'
In [11]: len(_)
Out[11]: 64
In [12]: a[0] == 2**63
Out[12]: True
In [13]: a[0] - 1
Out[13]: 9.223372036854776e+18
In [14]: a[0] - 1 == 2 ** 63
Out[14]: True
In [15]: a[0] -= 1
In [16]: a[0]
Out[16]: 9223372036854775808
In [17]: np.version.version
Out[17]: '1.24.2'
In binary increment by one will change the last bit from zero to one and one to zero, and going from one to zero will change the bit before the last bit, this will keep carry to the left until the leftmost bit goes from zero to one.
In unit64 if you want to subtract one from zero, the number zero can't get any smaller so it is treated as 2^64, and subtract one from it you get 2^64-1, which in binary is '1'*64 and 18446744073709551615 in decimal.
In [6]: a[0] - 1
Out[6]: 1.8446744073709552e+19
In [7]: a[0] - 1 == 2**64
Out[7]: True
Then when the value is operated with a Python int
it is converted to a float
1.8446744073709552e+19 which because of the limitation of the format, is actually 2^64.
In [8]: a[0] -= 1
<ipython-input-8-9ab639258820>:1: RuntimeWarning: invalid value encountered in cast
a[0] -= 1
In [9]: a
Out[9]: array([9223372036854775808], dtype=uint64)
Now this gets interesting, the maximum value uint64 can hold is 2^64 - 1, because 2 ^ 64 is one followed by 64 zeros in binary, so it can't be presented as is in uint64, it is in this case converted to zero before the decrement, as the last 64 bits in 2^64 are zeros.
That's why there is an warning.
But when doing the calculation, somehow it is converted to signed int64, and then converted to uint64 again.
The calculated result is -1, when stored in signed int64 form, is '1'+'0'*63
because the leftmost bit is used for the sign, and the number is negative if the sign bit is set.
Because one bit is used for the sign the maximum value of int64 is 2^63-1 which is 9223372036854775807 in decimal.
When the number negative one in int64 is converted to uint64 it is treated as 2^63 which is 9223372036854775808 in decimal, because the number holds a numerical value of 2^63.
Then the number stays there no matter how many decrements I do, because when the operations happen the uint64 type is converted to a float, which has a value of 2^63, and decrement by one cannot change that value.
OverflowError: long too big to convert
for the second decrement. What version of numpy is this, and in what environment? – NkrumahOverflowError: Python int too large to convert to C long
– Discountenanceb = np.zeros(1,np.uint64); b[0] = 1; a - b
give, as one may expect,array([18446744073709551614], dtype=uint64)
. Similarlya[0] -= np.uint64(1)
also works. I suspect the-=
is allowing a conversion to the right-hand side-value type – Discountenancea[0] -= 1
leaves the value at zero, whilea -= 1
sets the value to FF..FF. – Spoilsmanone = np.uint64(1)
also work as expected,a[0] -= one
even gives an overflow warning at the first subtraction. – Acieratea[0] -= 0
does the same for me. – Condylomaa[0] = 1152921504606847360; a[0] -= 1
results in1152921504606847488
( it is increasing). Comparing the bit representations of these numbers, it appears that they are truncated at the upper 52 bits after subtraction. Since this is the same length as the float64 fractional bits, this is probably what is happening. – Hydrobomb0x8000...
(INT_MIN / LONG_MIN), the "indefinite integer" value (felixcloutier.com/x86/cvtsd2si). AVX-512 FP to unsigned int conversion produces 0xFFFF... for out-of-range inputs. (felixcloutier.com/x86/vcvtsd2usi). – Pharmaceutical0xffff...
is a float with value-1
which is indeed out-of-range foruint64_t
, but the 2nd conversion is also out-of-range foruint64_t
. – Pharmaceutical