Probabilities and computer limitations |
|
| HOME |
There are limits to the size and precision of numbers which computers can
handle, and they can cause problems when we try to calculate
probabilities, and especially likelihoods. Numbers smaller than
10
-308
become zero and
numbers greater than 0.9999999999999999 become 1. Although not
important in the real world, these rounding errors mean that
there are large ranges of parameter values that have likelihoods
of 0 or 1. Our algorithms to find maximum likelihoods or
generate MCMC chains fail when they crash into the 1 cliff or
fall into the 0 abyss.
The solution is to work with logarithms of probabilities instead of the actual values [log(p) instead of p], eg, we routinely work with log-likelihoods. Multiplying probabilities is then simply a matter of adding up the logs. But sometimes we need to add up probabilities or calculate the complement, 1 - p, and we need to do that without falling in the 0 abyss or smashing into the 1 wall. Floating point numbersIn R, virtually all numbers are stored as floating point numbers, which use the same idea as scientific notation: a number such as 2017 will be stored as 2.017e3. The "2.017" part is the significand, the "3" is the exponent. It's a bit more complicated than that, because numbers are stored in binary form, not decimal, but the same idea is used. On most computers, 53 bits are used for the significand, which corresponds to about 16 decimal digits, and 11 for the exponent, which allows decimal values between about -308 and +308.
Because of the limits on the exponent, values outside the range of �1e308 are
represented in R by
Some example probabilities
We need some probabilities to play with, and we'll generate a
wide range of values using the
The last 4 values appear to be 1, but that may just be due to rounding when printed. We can check with:
The print-out is rounded to 1 for the 8th and 9th values, but
the last 2 really are one. Values in the range 1
� 1e-16 are rounded internally to 1; see
Using log probabilitiesWe will look at how we can use log-probabilities.
Er, no, that doesn't work, the last 2
values are now exactly 0. Most R functions which use
probabilities have a
Now we can work with probabilities from very near 0 to very near 1 without problems. In many applications, we need to multiply probabilities, and that just means that we add up the logs instead. But adding and subtracting probabilities still need care. Adding probabilitiesThis generally becomes a problem if you have a long vector of probabilities, but we need a short vector to be able to explore the options. This one will do:
All except the first value in this vector turn into a zero
when converted back to a probability, so we can't just use
A simple solution
A simple workaround is to scale all the probabilities so that
the biggest = 1, then scale back again after the addition. The
scaling can easily be done by subtracting the biggest
Now we can add up the values, convert back to a log, and add
back the biggest
A better version
We can still get into trouble if the sum of all the smaller
values add up to less than 2.2e-16 (or your value for
In this example, the small values add up to 0.58, so we don't really need the better version, but it does produce a more robust general purpose routine, which we can put into a function like this:
Hat tip: Bill Dunlap and Spencer at the R help forum . Update: This won't work if all the probabilities being added up are zero! See new post . Subtracting probabilities...or to be precise, calculating 1 - p. This is problematic when either p or 1 - p is close to 1. We'll use the original example data for this:
If we already know
If we don't have
A: We can used the
B: Or we can use the function
Let's compare the output from A and B with the value from reversing the logit:
As we expected, A works well when p is close to 1, B when p is close to zero. In the middle of the range, they are equally good, but using the wrong one near 1 or 0 is disastrous. So our general purpose function will include a check on the size of log_p:
The value -0.693 used is actually log(0.5), but calculating logs is expensive in computer time, so it's more efficient to just insert the value here, especially as it does not need to be exact. Hat tip: Martin M � chler and the Rmpfr package vignette .
|
|
Updated 4 August 2017 by Mike Meredith
|
|