Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modulo operation on a python negative decimal.Decimal and a positive int

With simple ints:

>>> -45 % 360
315

Whereas, using a decimal.Decimal:

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

I would expect to get Decimal('315').

Is there any reason for this? Is there a way to get a consistent behaviour (without patching decimal.Decimal)? (I did not change the context, and cannot find how it could be changed to solve this situation).

like image 485
zezollo Avatar asked Jan 19 '18 18:01

zezollo


People also ask

Can you do Modulo with decimals?

modulo doesn't work with decimals because it only accepts integers. As discussed above, you can define another procedure to see if two numbers are divisible by each other. Otherwise, the computer doesn't know how to accept non-integer numbers for modulo .

What is the formula for modulo Python?

The Python modulo operator calculates the remainder of dividing two values. This operator is represented by the percentage sign (%). The syntax for the modulo operator is: number1 % number2. The first number is divided by the second then the remainder is returned.

How do you separate integers and decimals in Python?

Use the math. modf() method to split a number into integer and decimal parts, e.g. result = math. modf(my_num) .


2 Answers

After a long search (because searching on "%", "mod", "modulo" etc. gives a thousand of results), I finally found that, surprisingly, this is intended:

There are some small differences between arithmetic on Decimal objects and arithmetic on integers and floats. When the remainder operator % is applied to Decimal objects, the sign of the result is the sign of the dividend rather than the sign of the divisor:

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

I don't know the reason for this, but it looks like it's not possible to change this behaviour (without patching).

like image 60
zezollo Avatar answered Sep 23 '22 21:09

zezollo


Python behaves according to IBM's General Decimal Arithmetic Specification.

The remainder is defined as:

remainder takes two operands; it returns the remainder from integer division. […]

the result is the residue of the dividend after the operation of calculating integer division as described for divide-integer, rounded to precision digits if necessary. The sign of the result, if non-zero, is the same as that of the original dividend.

So because Decimal('-45') // D('360') is Decimal('-0'), the remainder can only be Decimal('-45').

Though why is the quotient 0 and not -1? The specification says:

divide-integer takes two operands; it divides two numbers and returns the integer part of the result. […]

the result returned is defined to be that which would result from repeatedly subtracting the divisor from the dividend while the dividend is larger than or equal to the divisor. During this subtraction, the absolute values of both the dividend and the divisor are used: the sign of the final result is the same as that which would result if normal division were used. […]

Notes: […]

  1. The divide-integer and remainder operations are defined so that they may be calculated as a by-product of the standard division operation (described above). The division process is ended as soon as the integer result is available; the residue of the dividend is the remainder.

How many times can you subtract 360 from 45? 0 times. Is an integer result available? It is. Then the quotient is 0 with a minus sign because the divide operation says that

The sign of the result is the exclusive or of the signs of the operands.

As for why the Decimal Specification goes on this route, instead of doing it like in math where the remainder is always positive, I'm speculating that it could be for the simplicity of the subtraction algorithm. No need to check the sign of the operands in order to compute the absolute value of the quotient. Modern implementations probably use more complicated algorithms anyway, but simplicity could be have an important factor back in the days when the standard was taking form and hardware was simpler (way fewer transistors). Fun fact: Intel switched from radix-2 integer division to radix-16 only in 2007 with the release of Penryn.

like image 34
Cristian Ciupitu Avatar answered Sep 21 '22 21:09

Cristian Ciupitu