Math for AI course · Lesson 8 of 180

Logarithms for AI: Keep Tiny Probability Products from Vanishing

Learn logarithms as inverse powers, derive the product-to-sum laws, and use NumPy log scores to distinguish probability products that float64 rounds to zero.

Share this article

A small language model assigns probability 0.10.1 to each chosen token in a simplified sequence-scoring example. A sequence with 400 such choices has the mathematically nonzero product

0.1×0.1××0.1400 factors=(0.1)400=10400.\underbrace{0.1\times0.1\times\cdots\times0.1}_{400\text{ factors}} =(0.1)^{400}=10^{-400}.

Add one more equally likely token and the product is 1040110^{-401}. Those are different positive numbers, yet NumPy stores both products as 0.0 when it calculates them with 64-bit floating-point numbers. A ranking based on the stored products has lost the difference.

Logarithms preserve a more useful representation: the two products become the finite sums 400ln(0.1)921.034037400\ln(0.1)\approx-921.034037 and 401ln(0.1)923.336622401\ln(0.1)\approx-923.336622. By the end of this lesson, you will be able to read a logarithm as an inverse power, derive the laws that turn products into sums, calculate a short sequence score by hand, and implement the same idea without pretending that log space removes every numerical limit.

This lesson reverses the exponent operation developed in Lesson 7: Powers for AI, whose canonical English route belongs to the course’s four-locale Translation Set. The official Mathematics for Machine Learning companion divides its path into mathematical foundations and the machine-learning problems that use them. Its introductory chapter motivates building foundations before relying on probabilistic models; the logarithm mathematics below is an original probability bridge rather than a definition attributed to Chapter 1.

The missing answer is an exponent

A logarithm asks one precise question: which exponent produces this positive number from the chosen base?

For example, log2(8)=3\log_2(8)=3 because 23=82^3=8, while log10(0.01)=2\log_{10}(0.01)=-2 because 102=0.0110^{-2}=0.01. The negative answer is an exponent, not a negative input.

Three bases appear often in computing:

NameNotation used hereBaseOne useful reading
Common logarithmlog10(x)\log_{10}(x)1010Decimal orders of magnitude
Natural logarithmln(x)=loge(x)\ln(x)=\log_e(x)e2.71828e\approx2.71828Probability and optimization formulas
Binary logarithmlog2(x)\log_2(x)22Doublings and powers of two

Some fields write log without a base, but that convention is not universal. This lesson writes ln\ln for the natural logarithm and includes a subscript for other bases. NumPy follows the same practical choice: numpy.log computes the natural logarithm element by element.

The inverse relationship gives two checks whenever both expressions are in their real domains:

logb(by)=yandblogb(x)=x.\log_b(b^y)=y \qquad\text{and}\qquad b^{\log_b(x)}=x.

Multiplication leaves an additive trail

The product law is not a pattern to memorize in isolation. It falls directly out of the exponent law from Lesson 7.

Take positive numbers aa and cc, and use a valid logarithm base b>0b>0, b1b\ne1. Let

u=logb(a)andv=logb(c).u=\log_b(a)\quad\text{and}\quad v=\log_b(c).

The definition says a=bua=b^u and c=bvc=b^v. Multiplying and applying the same-base exponent law gives

ac=bubv=bu+v.ac=b^u b^v=b^{u+v}.

The exponent that produces acac is therefore u+vu+v:

logb(ac)=logb(a)+logb(c).\boxed{\log_b(ac)=\log_b(a)+\log_b(c)}.

Division subtracts exponents under the same assumptions:

ac=bubv=buvlogb ⁣(ac)=logb(a)logb(c).\frac{a}{c}=\frac{b^u}{b^v}=b^{u-v} \quad\Longrightarrow\quad \boxed{\log_b\!\left(\frac{a}{c}\right)=\log_b(a)-\log_b(c)}.

Raising a positive number to a real power rr multiplies its exponent:

ar=(bu)r=brulogb(ar)=rlogb(a).a^r=(b^u)^r=b^{ru} \quad\Longrightarrow\quad \boxed{\log_b(a^r)=r\log_b(a)}.

Positivity is doing real work in all three laws: a>0a>0 and c>0c>0 keep every real logarithm defined, and c>0c>0 also keeps the quotient denominator nonzero.

Addition does not follow the product law. For a quick counterexample,

ln(2+3)=ln(5)1.609438,\ln(2+3)=\ln(5)\approx1.609438,

but

ln(2)+ln(3)=ln(6)1.791759.\ln(2)+\ln(3)=\ln(6)\approx1.791759.

So log(a+c)log(a)+log(c)\log(a+c)\ne\log(a)+\log(c) in general. A stable operation for adding values that are already stored as logarithms appears later in this lesson, and it is deliberately different from product-to-sum conversion.

Four token choices, worked in both representations

Suppose two candidate continuations each contain four token choices. For this small calculation, treat each listed pip_i as the model’s positive conditional probability for the chosen token at position ii. Multiplying the four factors produces a sequence score; adding their natural logarithms produces the corresponding log-probability. For a fixed observed sequence under the model, these scores are also called its likelihood and log-likelihood. Later lessons will develop the full probability and estimation machinery; here the job is only to calculate and compare the two representations.

Candidate A has twice Candidate B’s product. Its log score is also larger: 7.824046>8.517193-7.824046>-8.517193. That ordering is guaranteed because ln\ln has base e>1e>1 and is strictly increasing. “Larger” among negative log scores means closer to zero. The score did not become a probability after the logarithm; it became a comparison-friendly representation of one.

Binary64 reaches zero while the log score keeps moving

Computers cannot store every real number. JavaScript’s Number and NumPy’s float64 use the binary64 floating-point format. NumPy reports its machine limits through numpy.finfo: the smallest positive normal float64 value is about 2.2250738585072014×103082.2250738585072014\times10^{-308}. Positive subnormal values fill part of the gap between that number and zero, but with reduced precision; the smallest positive subnormal is about 4.9406564584124654×103244.9406564584124654\times10^{-324}. numpy.nextafter confirms that this is the next representable float after zero in the positive direction.

Move either slider with a pointer or arrow keys. The upper panel follows direct binary64 multiplication through the normal region, the narrow subnormal strip, and zero. The lower panel follows the finite additive value nln(p)n\ln(p). The solid line with a circle and the dashed line with a square remain distinguishable without color, and the table exposes the selected state and transition rows.

Repeat one positive probability p for as many as 2,000 factors. The direct binary64 product can cross from normal to subnormal and then zero, while the cumulative natural-log sum remains finite. The readout uses four significant digits for tiny products rather than implying unavailable precision.

Choose p from 0.01 to 0.50. Arrow keys change it by 0.01.

0.10

Choose 1 to 2,000 repeated factors. Arrow keys change the count by one.

400
Direct product: solid line and circleLog sum: dashed line and squareSelected count: dotted guide
Direct probability product and cumulative log sumTwo vertically stacked plots share factor count on the horizontal axis. The upper solid line with a circle descends through normal and subnormal binary64 regions before continuing as a dotted zero line. The lower dashed line with a square remains a finite straight cumulative natural-log sum. Dotted vertical guides mark the selected count.Direct binary64 product and representability0-100-200-300Normal valuesSubnormal: narrow strip above the floorZero: dotted floorlog₁₀ magnitude of pⁿCumulative natural-log sum-0-1,151-2,303-3,454-4,60505001,0001,5002,000n ln(p)Number of factors n
Direct binary64 product
0
Cumulative log sum
-921.034037
Direct-product state
Zero
First subnormal factor count
308
First zero factor count
324

For p = 0.10 repeated 400 times, the direct binary64 product is 0 (Zero); the cumulative log sum is -921.034037.

For p = 0.10 repeated 400 times, the direct binary64 product is 0 (Zero); the cumulative log sum is -921.034037.

Tiny direct products are rounded to four significant digits in this display. The table and state labels, not extra decimal places, identify the representability transition.

Show selected and boundary values as a table
Direct binary64 products and cumulative natural-log sums at selected and representability milestones
Factor count nDirect productCumulative log sumDirect-product state
11.000e-1-2.302585Normal
3071.000e-307-706.893624Normal
3081.000e-308-709.196209Subnormal
3239.881e-324-743.734985Subnormal
3240-746.037570Zero
4000-921.034037Zero ← Selected
2,0000-4,605.170186Zero

At the server-rendered starting state, p=0.10p=0.10 and n=400n=400. Direct multiplication has already produced zero, but 400ln(0.10)=921.034037400\ln(0.10)=-921.034037\ldots is finite. The log value retains an additive score for the positive factors. Calling exp(921.034037)\exp(-921.034037\ldots) in float64 still returns zero, because changing representation cannot create a positive float64 value below the format’s range.

The precise transition depends on the data type, values, and order of floating- point operations. The explorer deliberately models repeated binary64 multiplication; it is not a universal cutoff for every probability program.

NumPy mirrors the safe path

The following program was run with NumPy 2.5.2. Direct multiplication uses np.prod, while log-space accumulation uses the element-wise natural logarithm documented by numpy.log followed by a sum.

import numpy as np

for n in (323, 324, 400, 401):
    factors = np.full(n, 0.1, dtype=np.float64)
    direct = np.prod(factors)
    log_score = np.log(factors).sum()
    print(f"n={n}: direct={direct:.4e}, log_score={log_score:.6f}")

limits = np.finfo(np.float64)
print(f"smallest normal:    {limits.smallest_normal:.4e}")
print(f"smallest subnormal: {limits.smallest_subnormal:.4e}")
print(f"next after zero:    {np.nextafter(0.0, 1.0):.4e}")
n=323: direct=9.8813e-324, log_score=-743.734985
n=324: direct=0.0000e+00, log_score=-746.037570
n=400: direct=0.0000e+00, log_score=-921.034037
n=401: direct=0.0000e+00, log_score=-923.336622
smallest normal:    2.2251e-308
smallest subnormal: 4.9407e-324
next after zero:    4.9407e-324

The 400- and 401-factor products collide at stored zero, while their log scores remain ordered. In practice, a system that needs to compare candidate products can keep the log scores and choose the larger one without exponentiating each score. This is the narrow numerical benefit: positive multiplicative factors become finite additive scores. It does not repair invalid inputs, rounding in every other operation, or a final conversion whose result is too small for the chosen type.

Adding alternative probabilities requires another identity. If x=ln(a)x=\ln(a) and y=ln(c)y=\ln(c), then

ln(a+c)=ln(ex+ey),\ln(a+c)=\ln(e^x+e^y),

not x+yx+y. NumPy’s numpy.logaddexp computes that log-of-a-sum directly. For two already logged alternatives, x=1000x=-1000 and y=1001y=-1001:

x, y = -1000.0, -1001.0

with np.errstate(divide="ignore"):
    naive = np.log(np.exp(x) + np.exp(y))

stable = np.logaddexp(x, y)
print(naive)
print(stable)
print(np.exp(stable))
-inf
-999.6867383124818
0.0

The naive route exponentiates too early, turning both alternatives into zero. np.logaddexp preserves their finite combined log score. Exponentiating that score still underflows, exactly as the final line shows. Use sum(log_factors) for a product of positive factors; use logaddexp when adding alternatives that are already represented by log values.

Boundaries worth making explicit

The real logarithm’s domain and base conditions prevent several common bugs:

Input or claimReal-log resultWhy
x>0x>0, b>0b>0, b1b\ne1ValidA unique real exponent exists
x=0x=0Undefined as a real logarithmNo finite exponent makes a positive base equal zero
x<0x<0Undefined in the real-number settingA valid real-log base raised to a real exponent stays positive
b=1b=1Invalid base1y=11^y=1 for every yy, so there is no inverse function
b0b\le0Invalid for this real-log definitionReal powers do not produce every positive argument consistently
logb(a+c)=logb(a)+logb(c)\log_b(a+c)=\log_b(a)+\log_b(c)False in generalThe right side equals logb(ac)\log_b(ac)

For real arrays, NumPy follows floating-point conventions around this boundary: np.log(0.0) returns -inf and signals a divide-by-zero condition, while np.log(-1.0) returns nan and signals an invalid operation. These are useful machine values, not permission to erase the mathematical distinction. A factor that is genuinely probability zero contributes -\infty to the log score; a positive product that rounded to floating-point zero should have been logged before multiplication. Once a computed product is already zero, np.log(product) cannot reconstruct the lost factors.

Silently replacing zeros with a small positive constant also changes the model’s stated values. If clipping is an intentional modeling decision, record the rule rather than presenting it as exact arithmetic.

Finally, ranking direction depends on the base. For every base b>1b>1—including ee, 22, and 1010—the logarithm is increasing, so the larger positive product has the larger log value. A valid base between zero and one gives a decreasing logarithm and reverses that ordering. Log-probability implementations normally use e>1e>1, which is why the less-negative log score ranks higher.

A finite score can be the useful result

The opening sequences did not need their microscopic probabilities converted back to decimal form to remain distinguishable. Their log scores preserved the multiplicative comparison as ordinary addition, and the product, quotient, and power laws explain why that representation works.

This foundation will support later lessons on probability, likelihood-based estimation, entropy, and stable model calculations. The next curriculum step changes focus to equations as constraints and the sets of values that satisfy them. For now, the practical stopping point is exact: keep positive factors in log space while combining and comparing them, and return to probability space only when the result is both necessary and representable.

Check your understanding

Question 1

Evaluate log₂(32) and log₁₀(0.001), then check each answer with a power.

Show the step-by-step solution

For log2(32)\log_2(32), ask which exponent on 22 produces 3232:

25=32log2(32)=5.2^5=32 \quad\Longrightarrow\quad \log_2(32)=5.

For log10(0.001)\log_{10}(0.001), rewrite the decimal as a power of ten:

0.001=11000=103log10(0.001)=3.0.001=\frac{1}{1000}=10^{-3} \quad\Longrightarrow\quad \log_{10}(0.001)=-3.

Both arguments are positive, and the bases 22 and 1010 are positive and not equal to 11, so the real logarithms satisfy the required assumptions. The checks are the powers 25=322^5=32 and 103=0.00110^{-3}=0.001.

Question 2

For x > 0 and y > 0, expand ln(x³/y²) into logarithms of x and y, naming the law used at each step.

Show the step-by-step solution

Start with the quotient law because the outer operation divides two positive quantities:

ln ⁣(x3y2)=ln(x3)ln(y2).\ln\!\left(\frac{x^3}{y^2}\right) =\ln(x^3)-\ln(y^2).

Then use the power law on each term:

ln(x3)ln(y2)=3ln(x)2ln(y).\ln(x^3)-\ln(y^2) =3\ln(x)-2\ln(y).

The assumptions x>0x>0 and y>0y>0 make x3x^3, y2y^2, and the quotient positive; they also ensure y20y^2\ne0. The expanded expression therefore has the same real domain as the original expression under the stated assumptions.

Question 3

A teammate writes ln(4 + 5) = ln(4) + ln(5). Disprove the claim and state the correct product identity.

Show the step-by-step solution

Evaluate what each side represents. The left side is

ln(4+5)=ln(9).\ln(4+5)=\ln(9).

The sum on the right uses the product law:

ln(4)+ln(5)=ln(4×5)=ln(20).\ln(4)+\ln(5)=\ln(4\times5)=\ln(20).

Because 9209\ne20, their natural logarithms are unequal. Numerically, ln(9)2.197225\ln(9)\approx2.197225 while ln(20)2.995732\ln(20)\approx2.995732. The valid identity is

ln(ac)=ln(a)+ln(c)\ln(ac)=\ln(a)+\ln(c)

for a>0a>0 and c>0c>0. It converts multiplication, not addition, into a sum.

Question 4

A three-token candidate has positive factors 0.5, 0.2, and 0.1. Compute its direct product and natural-log score step by step, then convert the log score back.

Show the step-by-step solution

Multiply from left to right:

0.5×0.2=0.1,0.1×0.1=0.01.0.5\times0.2=0.1, \qquad 0.1\times0.1=0.01.

For the log representation, calculate and add the three terms:

ln(0.5)0.693147,\ln(0.5)\approx-0.693147,ln(0.2)1.609438,ln(0.1)2.302585,\ln(0.2)\approx-1.609438, \qquad \ln(0.1)\approx-2.302585,0.6931471.6094382.302585=4.605170.-0.693147-1.609438-2.302585=-4.605170.

The product law says this sum equals ln(0.01)\ln(0.01). Applying the inverse exponential gives

e4.6051700.01.e^{-4.605170}\approx0.01.

The conversion agrees with the direct product; the small difference implied by the displayed decimals comes only from rounding the log terms to six decimal places.

Question 5

Two candidates have natural-log scores −40 and −42. Which product is larger, and by what multiplicative factor?

Show the step-by-step solution

The natural logarithm uses base e>1e>1, so it preserves ordering. Since 40>42-40>-42, the candidate with log score 40-40 has the larger positive product.

Let the products be A=e40A=e^{-40} and B=e42B=e^{-42}. Their ratio uses the quotient rule for exponents:

AB=e40e42=e40(42)=e27.389.\frac{A}{B}=\frac{e^{-40}}{e^{-42}}=e^{-40-(-42)}=e^2\approx7.389.

Thus the first candidate’s product is about 7.3897.389 times the second’s. The two-point difference in log space is a multiplicative ratio in the original space.

Question 6

At p = 0.1, the explorer shows a subnormal direct product at n = 323 and zero at n = 324. What happens to the log score, and what does this say about exponentiating it?

Show the step-by-step solution

The additive score is nln(p)n\ln(p). With ln(0.1)2.302585093\ln(0.1)\approx-2.302585093:

323ln(0.1)743.734985,323\ln(0.1)\approx-743.734985,324ln(0.1)746.037570.324\ln(0.1)\approx-746.037570.

Both are finite and remain different, even though direct binary64 multiplication reaches zero at 324 factors in the verified example. Log-space accumulation can therefore still compare the positive-factor sequences.

However, exponentiating 746.037570-746.037570 asks float64 to represent approximately 1032410^{-324}, below its positive range after rounding, so the stored result is zero. The log representation preserves the finite score; it does not expand the output range of binary64.

Question 7

Debug this code for adding two logged alternatives: np.log(np.exp(-1000) + np.exp(-1001)). Why does it return −inf, and which NumPy operation preserves the finite combined log score?

Show the step-by-step solution

The code exponentiates first. In float64, both intermediate values are too small to represent:

float64(e1000)=0,float64(e1001)=0.\operatorname{float64}(e^{-1000})=0, \qquad \operatorname{float64}(e^{-1001})=0.

The program then computes ln(0+0)=ln(0)\ln(0+0)=\ln(0), which NumPy represents as -inf while signaling divide by zero.

Use the operation designed for alternatives already in log space:

combined = np.logaddexp(-1000.0, -1001.0)
# -999.6867383124818

This computes ln(e1000+e1001)\ln(e^{-1000}+e^{-1001}) without first rounding both exponentials to zero. It is not the product rule: a product of positive factors uses np.log(factors).sum(), while this example adds two alternatives. Finally, np.exp(combined) still returns 0.0; the finite combined score is preserved, but its probability-scale value remains unrepresentable in float64.

Sources

  1. Mathematics for Machine Learning companion website
  2. Mathematics for Machine Learning book PDF
  3. NumPy documentation: numpy.log
  4. NumPy documentation: numpy.logaddexp
  5. NumPy documentation: numpy.finfo
  6. NumPy documentation: numpy.nextafter