Как устранить знак дополнительного минуса при округлении отрицательных чисел к нулю в numpy?

У меня есть простой вопрос о функциях fix и floor в numpy. При округлении отрицательных чисел, превышающих -1 к нулю, numpy округляет их до нуля, но оставляет отрицательный знак. Этот отрицательный знак мешает моей функции unique_rows costume, поскольку он использует ascontiguousarray для сравнения элементов массива, и этот знак нарушает уникальность. Оба раунда и исправления ведут себя одинаково в этом отношении.

>>> np.fix(-1e-6)
Out[1]: array(-0.0)
>>> np.round(-1e-6)
Out[2]: -0.0

Любые идеи о том, как избавиться от знака? Я подумал об использовании функции np.sign, но он поставляется с дополнительными вычислительными затратами.

Спасибо заранее.

Ответ 1

Проблема, с которой вы связаны между -0. и +0., является частью спецификации того, как должны работать поплавки (IEEE754). В некоторых случаях это нужно. См., Например, документы, которые связаны с документами для around.

Также стоит отметить, что два нуля должны сравниваться с равными, поэтому

np.array(-0.)==np.array(+0.) 
# True

То есть, я думаю, проблема более вероятна с вашим сопоставлением уникальности. Например:

a = np.array([-1., -0., 0., 1.])
np.unique(a)
#  array([-1., -0.,  1.])

Если вы хотите сохранить числа в виде с плавающей запятой, но все нули одинаковы, вы можете использовать:

x = np.linspace(-2, 2, 6)
#  array([-2. , -1.2, -0.4,  0.4,  1.2,  2. ])
y = x.round()
#  array([-2., -1., -0.,  0.,  1.,  2.])
y[y==0.] = 0.
#  array([-2., -1.,  0.,  0.,  1.,  2.])

# or  
y += 0.
#  array([-2., -1.,  0.,  0.,  1.,  2.])    

Обратите внимание, что вам нужно выполнить эту дополнительную работу, так как вы пытаетесь избежать спецификации с плавающей точкой.

Обратите также внимание, что это связано не с ошибкой округления. Например,

np.fix(np.array(-.4)).tostring().encode('hex')
# '0000000000000080'
np.fix(np.array(-0.)).tostring().encode('hex')
# '0000000000000080'

То есть результирующие числа будут точно такими же, но

np.fix(np.array(0.)).tostring().encode('hex')
# '0000000000000000'

отличается. Вот почему ваш метод не работает, поскольку он сравнивает двоичное представление чисел, которое отличается для двух нулей. Поэтому я считаю, что проблема скорее является методом сравнения, чем общей идеей сравнения чисел с плавающей запятой для уникальности.

Быстрый тест времени для различных подходов:

data0 = np.fix(4*np.random.rand(1000000,)-2)
#   [ 1. -0.  1. -0. -0.  1.  1.  0. -0. -0. .... ]

N = 100
data = np.array(data0)
print timeit.timeit("data += 0.", setup="from __main__ import np, data", number=N)
#  0.171831846237
data = np.array(data0)
print timeit.timeit("data[data==0.] = 0.", setup="from __main__ import np, data", number=N)
#  0.83500289917
data = np.array(data0)
print timeit.timeit("data.astype(np.int).astype(np.float)", setup="from __main__ import np, data", number=N)
#  0.843791007996

Я согласен с точкой @senderle в том, что если вы хотите простые и точные сравнения и можете обойтись с помощью int, ints будет проще. Но если вам нужны уникальные поплавки, вы тоже сможете это сделать, хотя вам нужно сделать это немного осторожнее. Основная проблема с поплавками заключается в том, что у вас могут быть небольшие различия, которые могут быть введены из вычислений и не отображаются в обычном print, но это не огромный барьер, и особенно не после round, fix, rint для разумного диапазона поплавков.

Ответ 2

Я думаю, что основная проблема заключается в том, что вы используете операции типа с множеством чисел с плавающей запятой, что следует избегать в качестве общего правила, если у вас нет очень веской причины и глубокого понимания с плавающей запятой номера.

Очевидная причина следовать этому правилу состоит в том, что даже очень маленькая разница между двумя поплавками регистрируется как абсолютная разница, поэтому численная ошибка может привести к тому, что операции типа типа приводят к неожиданным результатам. Теперь, в вашем случае использования, изначально может показаться, что вы избежали этой проблемы, округляя сначала, тем самым ограничивая диапазон возможных значений. Но оказывается, что неожиданные результаты все еще возможны, как показывает этот угловой случай. Числа с плавающей запятой трудно рассуждать.

Я думаю, что правильное исправление заключается в округлении, а затем для преобразования в int с помощью astype.

>>> a
array([-0.5,  2. ,  0.2, -3. , -0.2])
>>> numpy.fix(a)
array([-0.,  2.,  0., -3., -0.])
>>> numpy.fix(a).astype(int)    # could also use 'i8', etc...
array([ 0,  2,  0, -3,  0])

Поскольку вы уже округляете, это не должно отбрасывать информацию, и она будет более стабильной и предсказуемой для подобных операций позже. Это один из тех случаев, когда лучше использовать правильную абстракцию!

Если вам нужны поплавки, вы всегда можете конвертировать назад. Единственная проблема заключается в том, что он создает другую копию; но большую часть времени это не проблема. numpy достаточно быстр, чтобы накладные расходы на копирование были довольно маленькими!

Я добавлю, что если ваш случай действительно требует использования float, то tom10 ответ хороший. Но я чувствую, что число случаев, в которых как поплавки, так и операции типа типа действительно необходимы, очень невелико.