I have checked a few examples on the Python shell and they seem to spit out same numbers. But in a program where large set of numbers are supposed to be approximated, they apparently produce different results.

I am trying to write a little program that simulates movement of an object on a rectangular plane. For that, I had to write a class named "RectangularRoom" that takes in a width and a height and creates a grid:

class RectangularRoom(object):
"""
A RectangularRoom represents a rectangular region containing clean or dirty
tiles.
A room has a width and a height and contains (width * height) tiles. At any
particular time, each of these tiles is either clean or dirty.
"""
def __init__(self, width, height):
"""
Initializes a rectangular room with the specified width and height.
Initially, no tiles in the room have been cleaned.
width: an integer > 0
height: an integer > 0
"""
self.width = width
self.height = height
self.room_coordinates = []
for m in range(self.width):
for n in range(self.height):
self.room_coordinates.append((m,n))
self.cleaned = []
def cleanTileAtPosition(self, pos):
"""
Mark the tile under the position POS as cleaned.
Assumes that POS represents a valid position inside this room.
pos: a Position
"""
self.cleaned.append((int(pos.getX()), int(pos.getY())))
def isTileCleaned(self, m, n):
"""
Return True if the tile (m, n) has been cleaned.
Assumes that (m, n) represents a valid tile inside the room.
m: an integer
n: an integer
returns: True if (m, n) is cleaned, False otherwise
"""
assert type (m)== int and type (n) == int
return (m,n) in self.cleaned
def getNumTiles(self):
"""
Return the total number of tiles in the room.
returns: an integer
"""
return self.width*self.height
def getNumCleanedTiles(self):
"""
Return the total number of clean tiles in the room.
returns: an integer
"""
return len(self.cleaned)
def getRandomPosition(self):
"""
Return a random position inside the room.
returns: a Position object.
"""
return Position (random.randrange(0 , self.width), random.randrange(0 , self.height))
def isPositionInRoom(self, pos):
"""
Return True if pos is inside the room.
pos: a Position object.
returns: True if pos is in the room, False otherwise.
"""
return (int(pos.getX()), int(pos.getY())) in self.room_coordinates

As you see I implemented it using the int() method and the random generator "random.randrange".

In the solution, the teacher has implemented this class using the math.floor() function and the random generator random.random():

class RectangularRoom(object):
"""
A RectangularRoom represents a rectangular region containing clean or dirty
tiles.
A room has a width and a height and contains (width * height) tiles. At any
particular time, each of these tiles is either clean or dirty.
"""
def __init__(self, width, height):
"""
Initializes a rectangular room with the specified width and height.
Initially, no tiles in the room have been cleaned.
width: an integer > 0
height: an integer > 0
"""
self.width = width
self.height = height
self.tiles = {}
for x in range(self.width):
for y in range(self.height):
self.tiles[(x, y)] = False
def cleanTileAtPosition(self, pos):
"""
Mark the tile under the position POS as cleaned.
Assumes that POS represents a valid position inside this room.
pos: a Position
"""
x = math.floor(pos.getX())
y = math.floor(pos.getY())
self.tiles[(x, y)] = True
def isTileCleaned(self, m, n):
"""
Return True if the tile (m, n) has been cleaned.
Assumes that (m, n) represents a valid tile inside the room.
m: an integer
n: an integer
returns: True if (m, n) is cleaned, False otherwise
"""
return self.tiles[(m, n)]
def getNumTiles(self):
"""
Return the total number of tiles in the room.
returns: an integer
"""
return self.width * self.height
def getNumCleanedTiles(self):
"""
Return the total number of clean tiles in the room.
returns: an integer
"""
return sum(self.tiles.values())
def getRandomPosition(self):
"""
Return a random position inside the room.
returns: a Position object.
"""
return Position(random.random() * self.width,
random.random() * self.height)
def isPositionInRoom(self, pos):
"""
Return True if pos is inside the room.
pos: a Position object.
returns: True if pos is in the room, False otherwise.
"""
return ((0 <= pos.getX() < self.width)
and (0 <= pos.getY() < self.height))

Surprisingly, these two chunks of code produce completely different results. I want to know why this happens. int() and floor() should have the same effect on a positive number and the two random functions seem to produce similar numbers.

In python3 math.floor calls PyLong_FromDouble which is also called by int(x), but in python2 math.floor simple calls the Cfloor function, while int(x) does something more complex to assure consistent rounding(and it uses modf instead of floor), and so the returned value may be different.
–
BakuriuApr 17 '13 at 23:02

I don't know if this could really contribute to the problem he is having, but I'm interested: I can't find information about inconsistent rounding using C floor - can you elaborate or provide a link?
–
thebigdogApr 17 '13 at 23:09

1

What I meant is that C doesn't fully describe what should happen when casting a double to a long. (This may have changed with the new releases)
–
BakuriuApr 17 '13 at 23:19

You damn well know the answer to my question :D Thanks, the difference in results stemmed from the very fact that I allowed duplicate elements in the self.cleaned list. I changed the list to a set and now the output is the same as the one given by the teacher. Thanks for your helpful answer!
–
OmidApr 17 '13 at 23:22

There is one big difference between your approaches. random.randrange returns an integer, and you call int() on the positions. You are working with integers.
Meanwhile, random.random() * something returns a float, and math.floor also returns a float. Your teacher is working with floats all the time.

That is the at least the difference between int and math.floor, but I don't know why that would produce completely different results. Could you be a little more concrete about that?

Even though int() and floor() do the "same thing" for positive integers, the result is of a different type. This can result in all kinds of different interactions depending on the operations you do with them later on. Python will figure out how to make it "work" from its perspective, though it won't necessarily give you the results you want.

Generally when doing any kind of math in programming, you want to be consistent in the types you use so that you don't have to worry about typecasting and promotion and all that nasty stuff.

Using "integers" instead of math.floor terhe would be better - still, Python has enough black magic in float <-> integer comparisons that this difference alone should be no problem in the code above. The code however, in both versions has a distinct Java or C++ "accent" making it much more complictaed than simple Python code can be. It is likely the difference arises in some other point of the code - not on this rounding. For example - try using random.randint instead of getting a float and later rounding it, to start with.

Thanks, but I am sure that the problem arises from this specific part. The program is more than 300 lines long, but when I swap this particular part of my code with that of the teachers the results will be identical (Otherwise there is a big difference). The structure and the overall skeleton of the rest of the code is the same as the one given as the solution and I have checked the other parts a zillion times.
–
OmidApr 17 '13 at 22:58