Today I learned a new thing that forced me to reckon my understanding of Python. It is about //
. It is just not a double slash or typo. It is an operator called integer division. The detail about how this operator works is mentioned in PEP 238. So I came across the Code Signal test and stuck at this question:
I thought that the result of both snippets should be the same. Because in Python, it uses // and in Java, it uses int
data type. I don’t want to judge alone; you see that the question itself was downvoted a lot. Well, I downvoted it too!
Here are the answers:
I tried to run the Java (or C++) snippet with an online C++ compiler and played with a random number, but no clue. However, I know that the problem lies in how the rounding process. Maybe Java and C++ round the number to ceil instead of the floor, or vice versa. Fortunately, I gave up quickly and look at //
instead. As everybody knows, me included, that //
yields an integer:
5//4
# 1
1.25
3//2
# 1
17//3
# 5
while the /
yields a floating-point number:
5/4
#1.25
3/2
# 1.5
17/3
# 5.666666666666667
But I don’t know that //
is using floor
division! as mentioned on PEP 238 page:
Floor division will be implemented in all the Python numeric types and will have the semantics of:
a // b == floor(a/b)
Except that the result type will be the common type into which a and b are coerced before the operation.
https://www.python.org/dev/peps/pep-0238/#semantics-of-floor-division
Meh, maybe you already guess that, from two snippets before you noticed that 17//3
is 5
, instead of 6
. You may also think that it is maybe truncate
. But did you know that in negative numbers for example 17//-3
, the result is not -5
, but -6
? I did not, at least until today. This stuff is easy if you take a look at the image below:
I just realized that when dealing with a negative number, the floor
is toward greater digits, in a negatively way. Pun intended. That makes sense. Why 17//-3
is equal to -6
. Here are other examples:
import numpy
np.ceil((-1.9, -1.5, -1.1, 0, 1.1, 1.5, 1.9))
# array([-1., -1., -1., 0., 2., 2., 2.])
np.floor((-1.9, -1.5, -1.1, 0, 1.1, 1.5, 1.9))
# array([-2., -2., -2., 0., 1., 1., 1.])
np.round_((-1.9, -1.5, -1.1, 0, 1.1, 1.5, 1.9))
# array([-2., -2., -1., 0., 1., 2., 2.])
Now it is clear how //
works.
After digging the rabbit’s hole deeper, I can’t ignore how the round
function works. Just in case. Let’s take a look at the round
function. It is a bit tricky they said:
Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float. See Floating Point Arithmetic: Issues and Limitations for more information.
This is totally understandable since it is an unavoidable technical error about how a computer represents a floating-point number 1. Nevertheless, it is only matters if we work with something that requires cautious number operation.
NumPy also posted a special note about how NumPy round_()
(equivalent with around()
) function works:
For values exactly halfway between rounded decimal values, NumPy rounds to the nearest even value. Thus 1.5 and 2.5 round to 2.0, -0.5 and 0.5 round to 0.0, etc.
https://numpy.org/doc/stable/reference/generated/numpy.around.html#numpy.around
This blog post certainly will help me to remember that I should do a test when dealing with division and negative floating-point numbers. I hope it will help you too.