I am trying to calculate log(a + b)
given log(a)
and log(b)
. The problem is, log(a)
and log(b)
are so negative that when I try to calculate a
and b
themselves, they underflow and I get log(0)
, which is undefined.
For log(a * b)
and log(a / b)
, this isn't a problem, since log(a * b) = log(a) + log(b)
and log(a / b) = log(a) - log(b)
. Is there a similar method to calculate log(a + b)
without needing a
and b
themselves, avoiding the underflow?
In short, use the following expression:
where I am using
la
andlb
as the variables storinglog(a)
andlog(b)
respectively, and function names are from C'smath.h
. Most other languages will also have these functions, but they might be named differently, e.g.abs
andmax
instead offabs
andfmax
. (Note: These are conventions I will use throughout this answer.)Speaking of functions, Mark Dickinson has a good point: you might want to check whether you have access to one that will do this for you directly. For example, if you are using Python and NumPy, you have access to
logaddexp
, and SciPy haslogsumexp
.If you'd like more detail on where the above came from, how to add more than two numbers, and how to subtract, keep reading.
In more detail
There isn't a rule as simple as the ones for multiplication and division, but there is a mathematical identity that will help:
log(a + b) = log(a) + log(1 + b/a)
We can play with that identity a little to get the following:
There's still a problem here. If
la
is much greater thanlb
, you'll have1 + 0.000...000something
inside thelog
. There's not enough digits in the floating-point mantissa to store thesomething
, so you'll just getlog(1)
, losinglb
entirely.Luckily, most programming languages have a function in their standard library for solving just this problem,
log1p
, which calculates the logarithm of one plus its argument. That is,log1p(x)
returnslog(1 + x)
but in a way that's accurate for very smallx
.So, now we have:
We are almost there. There's one other thing to consider. In general, you want
la
to be greater thanlb
. It won't always matter, but sometimes this will gain you extra precision.* If the difference betweenlb
andla
is really big, this will save you from overflow inexp(lb - la)
. In the most extreme case, the calculation works whenlb
is negative infinity (i.e.b
is 0), but not whenla
is.Sometimes, you'll know which one is greater, and you can just use that one as
la
. But when it could be either one, you can use maximum and absolute value to work around it:Sum of a collection
If you need to take the sum of more than two numbers, we can derive an expanded version of the identity above:
We will want to use similar tricks as when we were adding two numbers. That way, we get the most accurate answer and avoid over- and underflow as much as possible. First,
log1p
:The other consideration is which operand to pull out in front of the
log1p
. I usedla[0]
for demonstration so far, but you want to use whichever one is greatest. This is for all the same reasons we wantedla > lb
when adding two numbers. For example, ifla[1]
were greatest, you would do the calculation like this:Putting this into proper code, it would look something like this (this is C, but it should translate well to other languages):
Calculating difference in log space
This wasn't part of the question, but since it may be useful nonetheless: subtraction in log space can be performed similarly. The basic formula is like this:
Note that this is still assuming
la
is greater thanlb
, but for subtraction, it's even more important. Ifla
is less thanlb
, you are taking the logarithm of a negative number!Similar to addition, this has an accuracy issue that can be fixed by using specialized functions, but it turns out there are two ways. One uses the same
log1p
function as above, but the other usesexpm1
, whereexpm1(x)
returnsexp(x) - 1
. Here are both ways:Which one you should use depends on the value of
-(lb - la)
. The first is more accurate when-(lb - la)
is greater than roughly 0.693 (i.e.log(2)
), and the second is more accurate when it's less. For more detail on why that is and wherelog(2)
came from, see this note from the R project evaluating the two methods.The end result would look like this:
or in function form:
* There is a bit of a sweet spot for this. When the difference between
la
andlb
is small, the answer will be accurate either way. When the difference is too big, the result will always equal the greater of the two, since floating point numbers don't have enough precision. But when the difference is just right, you'll get better accuracy whenla
is greater.