Как получить строки, в которых значение не делится на 0,01 — потенциальные проблемы с арифметикой с плавающей запятой

#python #pandas #floating-point #precision

#python #pandas #с плавающей запятой #точность

Вопрос:

У меня есть серия pandas со значениями с плавающей запятой, как показано ниже:

 s = pd.Series({0: 899.0,
  1: 899.0,
  2: 1099.0,
  3: 279.29998779296875,
  4: 2598.833251953125,
  5: 499.1666564941406,
  6: 1709.050048828125,
  7: 279.29998779296875,
  8: 999.0,
  9: 1498.9949951171875}, name="var")
  

Я бы хотел получить индексы всех значений, которые не делятся на 0,01, поэтому я попытался определить mask = (100 * s % 1) > 0 , что довольно хорошо работает с предоставленной серией примеров:

 s[mask] 
3     279.299988
4    2598.833252
5     499.166656
6    1709.050049
7     279.299988
9    1498.994995
Name: var, dtype: float64

s[~mask]
0     899.0
1     899.0
2    1099.0
8     999.0
Name: var, dtype: float64
  

Однако s здесь приведен лишь образец гораздо большего набора данных, и когда я делаю то же самое с исходным рядом, значение mask for 1709.050049 равно False , что указывает на то, что эти 0049 цифры являются просто проблемой представления с плавающей запятой для числа 1709.05 , которое было извлечено как таковое при создании s из моих исходных данных с pd.Series.to_dict() помощью метода. Поэтому мне интересно, верен ли мой способ маскировки чисел, неделимых на 0.01 ( mask = (100 * s % 1) > 0 ) . Если это не так, что не так с этим решением и как мы можем правильно замаскировать значения?

Комментарии:

1. Это выглядит как сложный способ избежать проблемы, которую следует решать по-другому… Какова ваша конечная цель?

2. Можете ли вы объяснить, что означает «неделимый на .01»?

3. @ThierryLathuille Получить числа, неделимые на 0,01, на самом деле является моей конечной целью. Эти числа представляют цены, и любая цена, которая не является кратностью 0.01 , вызывает подозрение в том смысле, что, возможно, была применена какая-то скидка или что-то в этом роде.

4. @SergeyBushmanov неделимый означает, что его нельзя разделить на 0.01 , т.Е. Это не кратность 0.01

5. Трудность заключается в том, что величина 0,01 не может быть точно представлена с плавающей запятой, потому что она не кратна степеням 2. Попробуйте умножить на 100 и преобразовать результат в decimal.Decimal .

Ответ №1:

Вы можете достичь желаемого с помощью np.isclose, установив параметр rtol :

 s = pd.Series({
    0: 898.999998,
    1: 899.0,
    2: 1099.0,
    3: 279.29998779296875,
    4: 2598.833251953125,
    5: 499.1666564941406,
    6: 1709.050048828125,
    7: 279.29998779296875,
    8: 999.0,
    9: 1498.9949951171875,
    10: 326.78}
    , name="var")

tolerance = 1e-12
mask = np.isclose(s, s.round(2),rtol = tolerance)
s[mask]
1      899.00
2     1099.00
8      999.00
10     326.78
Name: var, dtype: float64
  

Комментарии:

1. Это очень хорошая идея ( 1), спасибо за это! Однако, как и в одном из моих предыдущих комментариев, когда я умножаю 326.78 на 100 , я получаю 32677.999999999996 , а оператор modulo возвращает 0.999999999996362 значение, близкое к 1 , not 0 .