1

I'm getting something that doesn't seem to be making a lot of sense. I was practicing my coding by making a little program that would give me the probability of getting certain cards within a certain timeframe of a card game. In order to calculate the chances, I would need to create a method that would perform division, and report the chances as a fraction and as a decimal. so I designed this:

from fractions import Fraction
def time_odds(card_count,turns,deck_size=60):
    chance_of_occurence = float(card_count)/float(deck_size)
    opening_hand_odds = 7*chance_of_occurence
    turn_odds = (7 + turns)*chance_of_occurence
    print ("Chance of it being in the opening hand: %s or %s" %(opening_hand_odds,Fraction(opening_hand_odds)))
    print ("Chance of it being acquired by turn %s : %s or %s" %(turns,turn_odds,Fraction(turn_odds) ))

and then I used it like so:

time_odds(3,5)

but for whatever reason I got this as the answer:

"Chance of it being in the opening hand: 0.35000000000000003 or 
6305039478318695/18014398509481984"
"Chance of it being acquired by turn 5 : 0.6000000000000001 or 
1351079888211149/2251799813685248"

so it's like, almost right, except the decimal is just slightly off, giving like a 0.0000000000003 difference or a 0.000000000000000000001 difference.

Python doesn't do this when I just make it do division like this:

print (7*3/60)

This gives me just 0.35, which is correct. The only difference that I can observe, is that I get the slightly incorrect values when I am dividing with variables rather than just numbers.

I've looked around a little for an answer, and most incorrect division problems have to do with integer division (or I think it can be called floor division) , but I didn't manage to find anything addressing this.

I've had a similar problem with python doing this when I was dividing really big numbers. What's going on?

Why is this so? what can I do to correct it?

Hara
  • 1,467
  • 4
  • 18
  • 35
puistori
  • 76
  • 6

1 Answers1

3

The extra digits you're seeing are floating point precision errors. As you do more and more operations with floating point numbers, the errors have a chance of compounding.

The reason you don't see them when you try to replicate the computation by hand is that your replication performs the operations in a different order. If you compute 7 * 3 / 60, the mutiplication happens first (with no error), and the division introduces a small enough error that Python's float type hides it for you in its repr (because 0.35 unambiguously refers to the same float value as the computation). If you do 7 * (3 / 60), the division happens first (introducing error) and then the multiplication increases the size of the error to the point that it can't be hidden (because 0.35000000000000003 is a different float value than 0.35).

To avoid printing out the the extra digits that are probably error, you may want to explicitly specify a precision to use when turning your numbers into strings. For instance, rather than using the %s format code (which calls str on the value), you could use %.3f, which will round off your number after three decimal places.

There's another issue with your Fractions. You're creating the Fraction directly from the floating point value, which already has the error calculated in. That's why you're seeing the fraction print out with a very large numerator and denominator (it's exactly representing the same number as the inaccurate float). If you instead pass integer numerator and denominator values to the Fraction constructor, it will take care of simplifying the fraction for you without any floating point inaccuracy:

print("Chance of it being in the opening hand: %.3f or %s" 
      % (opening_hand_odds, Fraction(7*card_count, deck_size)))

This should print out the numbers as 0.350 and 7/20. You can of course choose whatever number of decimal places you want.

Completely separate from the floating point errors, the calculation isn't actually getting the probability right. The formula you're using may be a good enough one for doing in your head while playing a game, but it's not completely accurate. If you're using a computer to crunch the numbers for you, you might as well get it right.

The probability of drawing at least one of N specific cards from a deck of size M after D draws is:

1 - (comb(M-N, D) / comb(M, D))

Where comb is the binary coefficient or "combination" function (often spoken as "N choose R" and written "nCr" in mathematics). Python doesn't have an implementation of that function in the standard library, but there are a lot of add on modules you may already have installed that provide one or you can pretty easily write your own. See this earlier question for more specifics.

For your example parameters, the correct odds are '5397/17110' or 0.315.

Blckknght
  • 100,903
  • 11
  • 120
  • 169