2

I stumbled upon a weird edge behaviour of python 3's "flooring division" using //. After some calculations I arrive at the formula (a1 - a2) // b. Using

a1 = 226.72560000000001
a2 = 0.2856000000000165
b = 1.02
# all types are np.float64

I get a different result than had I used np.floor():

>>> (a1 - a2) // b
221.0
>>> np.floor((a1 - a2) / b)
222.0

This difference obviously stems from floating point calculation, but why do they behave differently? Using a higher precision calculator such as ke!san I can verify that //'s behaviour is mathematically correct, while for my calculation np.floor() would deliver the correct result.

YPOC
  • 521
  • 4
  • 20
  • 2
    [numpy.floor_divide](https://numpy.org/doc/stable/reference/generated/numpy.floor_divide.html#numpy-floor-divide) is consistent with ```//```, the use of ```numpy.floor``` is equivalent to ```(a1-a2)/b//1``` – Nin17 Jul 26 '23 at 10:05
  • 1
    You can use [divmod](https://docs.python.org/3/library/functions.html#divmod) instead of `//` to see how it is interpreted. Also, this happens with [math.floor](https://docs.python.org/3/library/math.html#math.floor), so it has nothing to do with numpy. – ken Jul 26 '23 at 11:28
  • [Related question.](https://stackoverflow.com/questions/35897621/why-1-0-05-results-in-19-0-in-python/35906988) – dan04 Jul 26 '23 at 20:50

3 Answers3

2

Indeed // result is mathematically correct.

But if you just do (a1 - a2) / b you can see the result is already rounded up to 222.0 (due to float storage limits, it should be 221.9999999999999936275 instead), so now using floor will return 222.0.

EDIT: Actually for //, first a1-a2 is rounded to 226.44 (instead of 226.4399999999999935) and then 226.44 // 1.02 mistakenly returns 221 instead of 222 due to @Talha Tayyab explanation (x//y returns (x-x%y)//y).
So there are two consecutive rounding errors which compensate each other and land on the correct result.

Mattravel
  • 1,358
  • 1
  • 15
1

If x is very close to an exact integer multiple of y, it’s possible for x//y to be one larger than (x-x%y)//y due to rounding. In such cases, Python returns the latter result, in order to preserve that divmod(x,y)[0] * y + x % y be very close to x

link to doc:

https://docs.python.org/3/reference/expressions.html#id10

What should the output of:

3.3//1.1

3.0 right?

wrong it is 2.0

So, when the numerator is very close to the multiple of denominator

x//y will return (x-x%y)//y:

a1 = 226.72560000000001
a2 = 0.2856000000000165
b = 1.02

x = a1-a2
y = 1.02

(x-x%y)//y

#output
221.0
Talha Tayyab
  • 8,111
  • 25
  • 27
  • 44
-1

remember, floating point arithmetic can be tricky, and it's always a good idea to be aware of its pitfalls. This is not a bug in Python or NumPy, but it is rather the nature of floating point numbers and the arithmetic operations that work on them.

The behavior you're watching is due to the contrasts in accuracy between diverse floating point sorts.

The term "drifting point" alludes to the way these numbers are spoken to inside. The subtle elements are very specialized, but the key point is that these numbers are spoken to employing a settled number of bits, and the more bits you have, got the more exactness you get.

Diverse sorts of floats in Python (or in libraries like NumPy) such as float16, float32, and float64 allude to floating point numbers that utilize 16, 32, and 64 bits separately.

A float64 (moreover known as "twofold exactness drift") has higher exactness and a bigger run than float32 ("single accuracy drift"), which in turn has higher exactness and larger range than float16 ("half accuracy float").

So, after you perform the same calculation utilizing different float sorts, you'll get somewhat diverse comes about due to these contrasts in exactness. Typically a common issue in numerical computations and it's something to be mindful of.

It's imperative to select the correct sort of float for your particular utilize case. In general, float64 could be a great default choice, since it offers a great adjust between accuracy and memory utilization. But on the off chance that you're working with exceptionally expansive datasets and memory utilization could be a concern, or on the off chance that you're performing computations on a GPU that underpins float32 or float16 computations, at that point it might make sense to utilize a lower-precision drift sort.

here is a little code to show you why

here is a little code to show you why :

import numpy as np

a1 = np.float16(226.72560000000001)
a2 = np.float16(0.2856000000000165)
b = np.float16(1.02)

a3= float(226.72560000000001)
a4 = float(0.2856000000000165)
b1 = float(1.02)

print((a3 - a4) // b1)
print((a1 - a2) // b)

print(np.floor((a3 - a4) / b1))
print(np.floor((a1 - a2) / b))

output :

221.0
222.0
222.0
222.0

You can change the type of float to see how it is affecting the result.

Harith 75
  • 19
  • 5
  • Someone did a bunch of random substitutions on your text, like "float" → "coast" and others, without you paying attention? It's still possible to understand, but much harder, and it looks funny. – anatolyg Jul 26 '23 at 10:24
  • it does if you use np.float64 the output will be 221.0 221.0 222.0 222.0 – Harith 75 Jul 26 '23 at 10:26
  • ok but that's not really answering the question just saying that ```(a1-a2)//b``` changes with precision when ```np.floor((a1-a2)/b)``` doesn't and in their case they are using the same precision for both – Nin17 Jul 26 '23 at 10:28
  • @anatolyg ups sorry english is not my tongue, edited thanks. – Harith 75 Jul 26 '23 at 10:30
  • @Nin17 hmm ok i see. Well in this case, Talha Tayyab answer is the best – Harith 75 Jul 26 '23 at 10:34