Skip to content

BUG: Fix binary_repr for negative numbers #7178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions doc/release/1.12.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,18 @@ Deprecations

Assignment of ndarray object's ``data`` attribute
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Assigning the 'data' attribute is an inherently unsafe operation as pointed
Assigning the 'data' attribute is an inherently unsafe operation as pointed
out in gh-7083. Such a capability will be removed in the future.

Unsafe int casting of the num attribute in linspace
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``np.linspace`` now raises DeprecationWarning when num cannot be safely
``np.linspace`` now raises DeprecationWarning when num cannot be safely
interpreted as an integer.

Insufficient bit width parameter to ``binary_repr``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a 'width' parameter is passed into ``binary_repr`` that is insufficient to
represent the number in base 2 (positive) or 2's complement (negative) form,
the function used to silently ignore the parameter and return a representation
using the minimal number of bits needed for the form in question. Such behavior
is now considered unsafe from a user perspective and will raise an error in the future.
90 changes: 46 additions & 44 deletions numpy/core/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -2090,31 +2090,6 @@ def isscalar(num):
else:
return type(num) in ScalarType

_lkup = {
'0':'0000',
'1':'0001',
'2':'0010',
'3':'0011',
'4':'0100',
'5':'0101',
'6':'0110',
'7':'0111',
'8':'1000',
'9':'1001',
'a':'1010',
'b':'1011',
'c':'1100',
'd':'1101',
'e':'1110',
'f':'1111',
'A':'1010',
'B':'1011',
'C':'1100',
'D':'1101',
'E':'1110',
'F':'1111',
'L':''}

def binary_repr(num, width=None):
"""
Return the binary representation of the input number as a string.
Expand All @@ -2134,8 +2109,18 @@ def binary_repr(num, width=None):
num : int
Only an integer decimal number can be used.
width : int, optional
The length of the returned string if `num` is positive, the length of
the two's complement if `num` is negative.
The length of the returned string if `num` is positive, or the length
of the two's complement if `num` is negative, provided that `width` is
at least a sufficient number of bits for `num` to be represented in the
designated form.

If the `width` value is insufficient, it will be ignored, and `num` will
be returned in binary(`num` > 0) or two's complement (`num` < 0) form
with its width equal to the minimum number of bits needed to represent
the number in the designated form. This behavior is deprecated and will
later raise an error.

.. deprecated:: 1.12.0

Returns
-------
Expand All @@ -2146,6 +2131,7 @@ def binary_repr(num, width=None):
--------
base_repr: Return a string representation of a number in the given base
system.
bin: Python's built-in binary representation generator of an integer.

Notes
-----
Expand All @@ -2169,27 +2155,43 @@ def binary_repr(num, width=None):
The two's complement is returned when the input number is negative and
width is specified:

>>> np.binary_repr(-3, width=4)
'1101'
>>> np.binary_repr(-3, width=3)
'101'
>>> np.binary_repr(-3, width=5)
'11101'

"""
# ' <-- unbreak Emacs fontification
sign = ''
if num < 0:
def warn_if_insufficient(width, binwdith):
if width is not None and width < binwidth:
warnings.warn(
"Insufficient bit width provided. This behavior "
"will raise an error in the future.", DeprecationWarning
)

if num == 0:
return '0' * (width or 1)

elif num > 0:
binary = bin(num)[2:]
binwidth = len(binary)
outwidth = (binwidth if width is None
else max(binwidth, width))
warn_if_insufficient(width, binwidth)
return binary.zfill(outwidth)

else:
if width is None:
sign = '-'
num = -num
return '-' + bin(-num)[2:]

else:
# replace num with its 2-complement
num = 2**width + num
elif num == 0:
return '0'*(width or 1)
ostr = hex(num)
bin = ''.join([_lkup[ch] for ch in ostr[2:]])
bin = bin.lstrip('0')
if width is not None:
bin = bin.zfill(width)
return sign + bin
poswidth = len(bin(-num)[2:])
twocomp = 2**(poswidth + 1) + num

binary = bin(twocomp)[2:]
binwidth = len(binary)
outwidth = max(binwidth, width)
warn_if_insufficient(width, binwidth)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just error out already in this branch? I am not sure, will let you pick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel more comfortable leaving it as a warning to keep the behaviour consistent for both positive and negative numbers. That was one of the problems brought up in the issue was the response was inconsistent between the two sides.

return '1' * (outwidth - binwidth) + binary

def base_repr(number, base=2, padding=0):
"""
Expand Down
34 changes: 30 additions & 4 deletions numpy/core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,21 +579,47 @@ def test_data_attr_assignment(self):


class TestLinspaceInvalidNumParameter(_DeprecationTestCase):
"""Argument to the num parameter in linspace that cannot be
"""Argument to the num parameter in linspace that cannot be
safely interpreted as an integer is deprecated in 1.12.0.

Argument to the num parameter in linspace that cannot be
safely interpreted as an integer should not be allowed.
Argument to the num parameter in linspace that cannot be
safely interpreted as an integer should not be allowed.
In the interest of not breaking code that passes
an argument that could still be interpreted as an integer, a
DeprecationWarning will be issued for the time being to give
DeprecationWarning will be issued for the time being to give
developers time to refactor relevant code.
"""
def test_float_arg(self):
# 2016-02-25, PR#7328
self.assert_deprecated(np.linspace, args=(0, 10, 2.5))


class TestBinaryReprInsufficientWidthParameterForRepresentation(_DeprecationTestCase):
"""
If a 'width' parameter is passed into ``binary_repr`` that is insufficient to
represent the number in base 2 (positive) or 2's complement (negative) form,
the function used to silently ignore the parameter and return a representation
using the minimal number of bits needed for the form in question. Such behavior
is now considered unsafe from a user perspective and will raise an error in the future.
"""

def test_insufficient_width_positive(self):
args = (10,)
kwargs = {'width': 2}

self.message = ("Insufficient bit width provided. This behavior "
"will raise an error in the future.")
self.assert_deprecated(np.binary_repr, args=args, kwargs=kwargs)

def test_insufficient_width_negative(self):
args = (-5,)
kwargs = {'width': 2}

self.message = ("Insufficient bit width provided. This behavior "
"will raise an error in the future.")
self.assert_deprecated(np.binary_repr, args=args, kwargs=kwargs)


class TestTestDeprecated(object):
def test_assert_deprecated(self):
test_case_instance = _DeprecationTestCase()
Expand Down
19 changes: 16 additions & 3 deletions numpy/core/tests/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,12 +1021,25 @@ class TestBinaryRepr(TestCase):
def test_zero(self):
assert_equal(np.binary_repr(0), '0')

def test_large(self):
assert_equal(np.binary_repr(10736848), '101000111101010011010000')
def test_positive(self):
assert_equal(np.binary_repr(10), '1010')
assert_equal(np.binary_repr(12522),
'11000011101010')
assert_equal(np.binary_repr(10736848),
'101000111101010011010000')

def test_negative(self):
assert_equal(np.binary_repr(-1), '-1')
assert_equal(np.binary_repr(-1, width=8), '11111111')
assert_equal(np.binary_repr(-10), '-1010')
assert_equal(np.binary_repr(-12522),
'-11000011101010')
assert_equal(np.binary_repr(-10736848),
'-101000111101010011010000')

def test_sufficient_width(self):
assert_equal(np.binary_repr(0, width=5), '00000')
assert_equal(np.binary_repr(10, width=7), '0001010')
assert_equal(np.binary_repr(-5, width=7), '1111011')


class TestBaseRepr(TestCase):
Expand Down