1

Problem: to see when computer makes approximation in mathematical calculations when I use Python

Example of the problem:

My old teacher once said the following statement

You cannot never calculate 200! with your computer. 

I am not completely sure whether it is true or not nowadays. It seems that it is, since I get a lot zeros for it from a Python script.

How can you see when your Python code makes approximations?

Community
  • 1
  • 1
Léo Léopold Hertz 준영
  • 134,464
  • 179
  • 445
  • 697
  • 2
    It's not very hard to compute 200! these days. See http://www53.wolframalpha.com/input/?i=200! – David Locke Jun 25 '09 at 00:03
  • Note that the URL above is supposed to include the "!" character. I'm not sure why SO doesn't include it as part of the link. – David Locke Jun 25 '09 at 00:05
  • It's generally good practice when auto-linking text to ignore punctuation (other than forward slash) at the end of a URL. 99% of the time, the user has included a non-punctuated URL in parentheses or in a sentence, and including the punctuation would break the link. You've just had the misfortune of being one of the remaining 1% of cases. – Charles Miller Jun 25 '09 at 00:18

6 Answers6

7

Python use arbitrary-precision arithmetic to calculate with integers, so it can exactly calculate 200!. For real numbers (so-called floating-point), Python does not use an exact representation. It uses a binary representation called IEEE 754, which is essentially scientific notation, except in base 2 instead of base 10.

Thus, any real number that cannot be exactly represented in base 2 with 53 bits of precision, Python cannot produce an exact result. For example, 0.1 (in base 10) is an infinite decimal in base 2, 0.0001100110011..., so it cannot be exactly represented. Hence, if you enter on a Python prompt:

>>> 0.1
0.10000000000000001

The result you get back is different, since has been converted from decimal to binary (with 53 bits of precision), back to decimal. As a consequence, you get things like this:

>>> 0.1 + 0.2 == 0.3
False

For a good (but long) read, see What Every Programmer Should Know About Floating-Point Arithmetic.

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
2

Python has unbounded integer sizes in the form of a long type. That is to say, if it is a whole number, the limit on the size of the number is restricted by the memory available to Python.

When you compute a large number such as 200! and you see an L on the end of it, that means Python has automatically cast the int to a long, because an int was not large enough to hold that number.

See section 6.4 of this page for more information.

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
  • AND... there's no "approximation" required for this. – S.Lott Jun 25 '09 at 01:08
  • By the way, in Python 3.0 there is no distinction between "regular" integers and "long" integers. The system translates seamlessly between them, so there are no L's anymore. – Tyler Jun 25 '09 at 15:25
1

See Handling very large numbers in Python.

Python has a BigNum class for holding 200! and will use it automatically.

Your teacher's statement, though not exactly true here is true in general. Computers have limitations, and it is good to know what they are. Remember that every time you add another integer of data storage, you can store a number that is 2^32 (4 billion +) times larger. It is hard to comprehend how many more numbers that is - but maths gets slower as you add more integers to store the exact value of a very large number.

As an example (what you can store with 1000 bits)

>>> 2 << 1000
2143017214372534641896850098120003621122809623411067214887500776740702102249872244986396
7576313917162551893458351062936503742905713846280871969155149397149607869135549648461970
8421492101247422837559083643060929499671638825347975351183310878921541258291423929553730
84335320859663305248773674411336138752L

I tried to illustrate how big a number you can store with 10000 bits, or even 8,000,000 bits (a megabyte) but that number is many pages long.

Community
  • 1
  • 1
Tom Leys
  • 18,473
  • 7
  • 40
  • 62
  • @Tom: How can you test that you cannot do % 1 + 200! %? I run the following code unsuccessfully, although 200! is more than 2^32 larger than 1: http://dpaste.com/59409/ – Léo Léopold Hertz 준영 Jun 25 '09 at 01:00
  • Masi, I think by "another integer of storage" Tom means "another 4 bytes of storage space" not adding another actual integer to an existing one. – Tyler Jun 25 '09 at 15:27
  • That is indeed what I meant. My point is that there are a lot of possible combinations available in a very small amount of memory. For example, there are only 10^69 atoms in the universe, a number that is smaller than 2^230 (there are 256 bytes in a single L2 cache line on the pentium 4 CPU). Imagine how many there are in a megabyte of memory. – Tom Leys Jun 25 '09 at 23:03
1

200! is a very large number indeed.

If the range of an IEEE 64-bit double is 1.7E +/- 308 (15 digits), you can see that the largest factorial you can get is around 170!.

Python can handle arbitrary sized numbers, as can Java with its BigInteger.

duffymo
  • 305,152
  • 44
  • 369
  • 561
1

Without some sort of clarification to that statement, it's obviously false. Just from personal experience, early lessons in programming (in the late 1980s) included solving very similar, if not exactly the same, problems. In general, to know some device which does calculations isn't making approximations, you have to prove (in the math sense of a proof) that it isn't.

Python's integer types (named int and long in 2.x, both folded into just the int type in 3.x) are very good, and do not overflow like, for example, the int type in C. If you do the obvious of print 200 * 199 * 198 * ... it may be slow, but it will be exact. Similiarly, addition, subtraction, and modulus are exact. Division is a mixed bag, as there's two operators, / and //, and they underwent a change in 2.x—in general you can only treat it as inexact.

If you want more control yet don't want to limit yourself to integers, look at the decimal module.

1

Python handles large numbers automatically (unlike a language like C where you can overflow its datatypes and the values reset to zero, for example) - over a certain point (sys.maxint or 2147483647) it converts the integer to a "long" (denoted by the L after the number), which can be any length:

>>> def fact(x):
...     return reduce(lambda x, y: x * y, range(1, x+1))
... 
>>> fact(10)
3628800
>>> fact(200)
788657867364790503552363213932185062295135977687173263294742533244359449963403342920304284011984623904177212138919638830257642790242637105061926624952829931113462857270763317237396988943922445621451664240254033291864131227428294853277524242407573903240321257405579568660226031904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000L

Long numbers are "easy", floating point is more complicated, and almost any computer representation of a floating point number is an approximation, for example:

>>> float(1)/3
0.33333333333333331

Obviously you can't store an infinite number of 3's in memory, so it cheats and rounds it a bit..

You may want to look at the decimal module:

  • Decimal numbers can be represented exactly. In contrast, numbers like 1.1 do not have an exact representation in binary floating point. End users typically would not expect 1.1 to display as 1.1000000000000001 as it does with binary floating point.
  • Unlike hardware based binary floating point, the decimal module has a user alterable precision (defaulting to 28 places) which can be as large as needed for a given problem
dbr
  • 165,801
  • 69
  • 278
  • 343
  • @dbr: Do you mean that because Python needs to use binary numbers, we get 1.10000000000000000000000001? – Léo Léopold Hertz 준영 Jun 25 '09 at 00:52
  • 1
    1.11 is closer to the actual value that is stored by the computer, but as of Python 3.1 they're simplifying the representation to make it less surprising for people who don't realize that: http://docs.python.org/dev/py3k/whatsnew/3.1.html – Tyler Jun 25 '09 at 15:30