Операция по модулю для python с отрицательным десятичным числом.Десятичное число и положительное значение int

#python #numbers #decimal

#python #python-3.x #десятичное число

Вопрос:

С помощью простых int s:

 >>> -45 % 360
315
  

Тогда как, используя decimal.Decimal :

 >>> from decimal import Decimal
>>> Decimal('-45') % 360
Decimal('-45')
  

Я бы ожидал получить Decimal('315') .

Есть ли для этого какая-либо причина? Есть ли способ получить согласованное поведение (без исправлений decimal.Decimal )? (Я не менял контекст и не могу найти, как его можно изменить, чтобы решить эту ситуацию).

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

1. Вы говорите так, как будто исправление — это нечто экстремальное, и его следует избегать, но такие языки, как Python, дают вам много возможностей для этого в вашем собственном коде (подклассы, волшебные методы «dunder», обертки и т. Д.). И, конечно, вы всегда можете сделать простой, но эффективный обходной путь написанияваша собственная функция для использования вместо встроенного синтаксиса.

2. @JohnY Извините за это, я не хотел так звучать; мое намерение состояло в том, чтобы вопрос не понимался как «Как исправить десятичное число, чтобы получить такое поведение?», Но вместо этого: «Есть ли причина для такого поведения?» и «Есть ли уже реализованное средствочтобы изменить это поведение (например, через настройки контекста)? «. Возможно, мне следовало упомянуть, что я уже реализовал небольшое обходное решение там, где оно мне было нужно.

Ответ №1:

После долгого поиска (потому что поиск по «%», «mod», «modulo» и т. Д. Дает тысячу результатов), я, наконец, обнаружил, что, к удивлению, это предназначено:

Существуют некоторые небольшие различия между арифметикой для десятичных объектов и арифметикой для целых чисел и чисел с плавающей запятой. Когда оператор остатка % применяется к десятичным объектам, знаком результата является знак делимого, а не знак делителя:

 >>> (-7) % 4
1
>>> Decimal(-7) % Decimal(4)
Decimal('-3')
  

Я не знаю причины этого, но похоже, что изменить это поведение невозможно (без исправления).

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

1. Decimal Использует ли math.fmod() ? Они оба имеют одинаковое поведение. Из документации Python : «По этой причине функция fmod() обычно предпочтительнее при работе с числами с плавающей запятой, в то время как Python x % y предпочтительнее при работе с целыми числами».

2. @Piinthesky Из того, что я понимаю в исходном коде библиотеки, это так import math as _math , но затем _math используется только для создания Decimal экземпляра из float . __mod__ (а также другие методы, например __truediv__ ) вычисляет частное и остаток от вызова Decimal._divide() , который полагается на divmod вызываемое на целых числах. Тем не менее, это не дает того же результата… Обратите внимание, что: divmod(-45, 360) == (-1, 315) но divmod(Decimal('-45'), Decimal('360')) == (Decimal('-0'), Decimal('-45')) .

3. Я думаю, что @Mr .T находится на правильном пути, упомянув fmod . Даже если реализация Decimal вообще не затрагивает fmod , кажется, по крайней мере, правдоподобным, что целью было подражать поведению fmod .

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

Ответ №2:

Python ведет себя в соответствии с общей спецификацией десятичной арифметики IBM.

Остаток определяется как:

остаток принимает два операнда; он возвращает остаток от целочисленного деления. […]

результатом является остаток от дивиденда после операции вычисления целочисленного деления, как описано для divide-integer, округленный до точных цифр, если необходимо. Знак результата, если он не равен нулю, такой же, как у исходного дивиденда.

Так что, поскольку Decimal('-45') // D('360') есть Decimal('-0') , остаток может быть только Decimal('-45') .

Хотя почему частное равно 0, а не -1? В спецификации говорится:

divide-integer принимает два операнда; он делит два числа и возвращает целочисленную часть результата. […]

возвращаемый результат определяется как результат многократного вычитания делителя из дивиденда, в то время как дивиденд больше или равен делителю. Во время этого вычитания используются абсолютные значения как делимого, так и делителя: знак конечного результата такой же, как и при использовании обычного деления. […]

Примечания: […]

  1. Операции деления целого числа и остатка определены таким образом, что они могут быть вычислены как побочный продукт стандартной операции деления (описанной выше). Процесс деления завершается, как только доступен целочисленный результат; остаток от дивиденда является остатком.

Сколько раз вы можете вычесть 360 из 45? 0 раз. Доступен ли целочисленный результат? Это так. Тогда частное равно 0 со знаком минус, потому что операция деления говорит, что

Знаком результата является исключающее или из знаков операндов.

Что касается того, почему десятичная спецификация идет по этому маршруту, вместо того, чтобы делать это, как в математике, где остаток всегда положительный, я предполагаю, что это может быть для простоты алгоритма вычитания. Нет необходимости проверять знак операндов, чтобы вычислить абсолютное значение частного. Современные реализации, вероятно, в любом случае используют более сложные алгоритмы, но простота могла иметь важный фактор в те дни, когда стандарт принимал форму, а аппаратное обеспечение было проще (намного меньше транзисторов). Забавный факт: Intel перешла с целочисленного деления radix-2 на radix-16 только в 2007 году с выпуском Penryn.