On Wed, May 17, 2006 at 04:22:32PM +0200, Koen Bok wrote:
> I have this very simple unit test, but it fails!
>
> Is this normal?
>
> import unittest
>
> class OrderProduct(object):
> def total(self):
> return self.quantity * self.price
>
> class CaseCheck(unittest.TestCase):
>
> def setUp(self):
> self.orderproduct = OrderProduct()
> self.orderproduct.product = Product()
> self.orderproduct.product.tax = 19
>
> self.test_data = [
> {'quantity': 6, 'price': 12.78, 'total': 76.68},
> {'quantity': 78, 'price': 12.78, 'total': 996.84},
> ]
>
> def testTaxSanity(self):
> """Description of the test"""
> for t in self.test_data:
> self.orderproduct.quantity = t['quantity']
> self.orderproduct.price = t['price']
> self.assertEqual(self.orderproduct.total(),
> t['total'])
>
> if __name__ == "__main__":
> unittest.main()
>
> F
> ======================================================================
> FAIL: Description of the test
> 
> Traceback (most recent call last):
> File "/Users/koen/unit_test.py", line 21, in testTaxSanity
> self.assertEqual(self.orderproduct.total(), t['total'])
> AssertionError: 76.679999999999993 != 76.680000000000007
>
> 
> Ran 1 test in 0.001s
>
> FAILED (failures=1)
This is normal, and has nothing to do with PyObjC, but rather is the
consequence of how floating point values are stored by a computer.
When you write "76.68", you mean "76 + 68/100". The decimal system of
writing number can thus only express exactly some subset of all rational
numbers. For example, "1/3" is a rational number, but you can't write it
in decimal. If you try, you find it is "0.333333333333...".
Likewise, computers store floating point values somewhat like fractions,
only instead of powers of 10 as the denominator, they use powers of 2.
Thus, some numbers that can be written in a base 10 decimal system cat
not be written with a finite number of digits in a base 2 decimal
system.
When whatever language you use (python in this case) parses "76.68" or
whatever in your source code, it picks the binary representation that is
closest to that number, which might be very slightly different. This is
why your calculations are 0.00000000000001 in error.
To avoid this issue, you have at least two solutions:
 Round the final results to two decimal places, and hope that the
accumilated error will never be more than 0.005 in error. Accountants
will glare at you since this is a gamble, but it is the easiest to
code, and fastest to compute.
 Use a class designed for doing base 10 decimal arithmetic. Python has
the decimal module, Foundation has NSDecimalNumber. This is only a
little more coding effort, but your results will be identical to the
results you would have obtained doing the math with traditional
methods on paper. Arithmetic operations are signifantly slower to
compute, but if you are just adding and multiplying a handfull of
numbers, we are talking in terms of microseconds.
