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:

Fig. 1. What, wait?!

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:

Fig. 2. 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:

# 1
# 1
# 5

while the / yields a floating-point number:

# 1.5
# 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.

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:

Fig. 3. Number stuff

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.

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.

  1. ↩︎