<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>John D. Cook</title>
	<atom:link href="http://www.johndcook.com/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.johndcook.com/blog</link>
	<description>Applied Mathematics Consulting</description>
	<lastBuildDate>Fri, 05 Jun 2026 12:04:28 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.johndcook.com/wp-content/uploads/2020/01/cropped-favicon_512-32x32.png</url>
	<title>John D. Cook</title>
	<link>https://www.johndcook.com/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Mr. Bessel&#8217;s eponymous functions</title>
		<link>https://www.johndcook.com/blog/2026/06/05/mr-bessels-eponymous-functions/</link>
					<comments>https://www.johndcook.com/blog/2026/06/05/mr-bessels-eponymous-functions/#respond</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Fri, 05 Jun 2026 12:04:28 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Special functions]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247111</guid>

					<description><![CDATA[<p>Yesterday I wrote a post showing that the trapezoid rule evaluates the integral very efficiently. But how do we know what the exact integral is for comparison? If you ask Mathematica, it will tell you the integral equals −2π J1(1) where J1 is a Bessel function. This may seem like rabbit out of a hat, [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/05/mr-bessels-eponymous-functions/">Mr. Bessel’s eponymous functions</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>Yesterday I wrote a post showing that the trapezoid rule evaluates the integral</p>
<p><img decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cossinint.svg" alt="\int_{-\pi}^\pi \cos(\sin(x) + x)\, dx" width="184" height="45" /></p>
<p>very efficiently. But how do we know what the exact integral is for comparison? If you ask Mathematica, it will tell you the integral equals −2π <em>J</em><sub>1</sub>(1) where <em>J</em><sub>1</sub> is a Bessel function. This may seem like rabbit out of a hat, but it&#8217;s actually a simple calculation given the integral definition of Bessel functions:</p>
<p><img decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/bessel_integral_def.svg" alt="J_n(z) = \frac{1}{\pi}\int_0^\pi \cos(n\theta - z\sin(\theta))\, d\theta" width="289" height="45" /></p>
<p>Since cosine is even, we can write our integral over [−π, π] as twice the integral over [0, π]. Then a change of variables turns this into the definition of <em>J</em><sub><em>n</em></sub>(<em>z</em>) with <em>n</em> = 1 and <em>z</em> = 1.</p>
<p>A deeper question is what have we accomplished by just giving a new name to essentially the same problem we started with. Another question is why in the world are Bessel functions defined as above.</p>
<p>As for what we&#8217;ve accomplished, we&#8217;ve related out integration problem to a very well-studied function. Bessel functions have been studied for two centuries and it&#8217;s easy to find software to evaluate them. Even the usually minimalist command line calculator <code>bc</code> has a function <code>j(x, n)</code> for evaluating <em>J</em><sub><em>n</em></sub>(<em>x</em>) for integer values of <em>n</em>. We could calculate our integral to 50 decimal places as follows.</p>
<pre>~$ bc -l
&gt;&gt;&gt; scale = 50
&gt;&gt;&gt;  -8*a(1)*j(1,1)
-2.76491937476833705153256665538788207487495025542883
</pre>
<p>Note that <code>bc</code> doesn&#8217;t have a value of π built in, but <code>a(x)</code> evaluates the arctangent function, and π = 4 arctan(1).</p>
<p>There are multiple ways of defining Bessel functions. The three main ways would be in terms of their power series, in terms of the differential equations they solve, and in terms of their integral representation. Friedrich Bessel defined what we now call Bessel functions of the first kind, the <em>J</em><sub><em>n</em></sub> functions, in terms of their integral representations.</p>
<p>Why did Bessel care about these integrals? They came out of his calculations in celestial mechanics. One example of this is solving <a href="https://www.johndcook.com/blog/2018/12/21/contraction-mapping-theorem/">Kepler&#8217;s equation</a> with Fourier series; the Fourier coefficients are given by Bessel functions. Bessel functions had occurred in applications before Mr. Bessel drew attention to them and studied them methodically.</p>
<p>Mathematics is developed inductively but taught deductively. It&#8217;s common for things to be kicked around for years before someone decides they deserve a name and systematic study. See this post on the <a href="https://www.johndcook.com/blog/2010/01/05/how-the-central-limit-theorem-began/">central limit theorem</a> for another example. The CLT is older than the Gaussian distribution, even older than Gauss.</p>
<h2>Related posts</h2>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/2016/02/17/analyzing-an-fm-signal/">FM radio and Bessel functions</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2021/06/12/vibrating-circular-membranes/">Vibrating circular membranes</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2017/11/04/fourier-bessel-series-and-gibbs-phenomena/">Fourier-Bessel series</a></li>
</ul>The post <a href="https://www.johndcook.com/blog/2026/06/05/mr-bessels-eponymous-functions/">Mr. Bessel’s eponymous functions</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/06/05/mr-bessels-eponymous-functions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The Latin of Linux</title>
		<link>https://www.johndcook.com/blog/2026/06/04/the-latin-of-linux/</link>
					<comments>https://www.johndcook.com/blog/2026/06/04/the-latin-of-linux/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 20:12:11 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247093</guid>

					<description><![CDATA[<p>One reason people study Latin is that it is the ancestor of many modern languages. English derives from West Germanic languages, not from Latin, but much of English vocabulary, perhaps as much as 60%, derives from Latin, either directly or indirectly through French. Knowing a bit of Latin makes sense of many things that would [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/04/the-latin-of-linux/">The Latin of Linux</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>One reason people study Latin is that it is the ancestor of many modern languages. English derives from West Germanic languages, not from Latin, but much of English vocabulary, perhaps as much as 60%, derives from Latin, either directly or indirectly through French.</p>
<p>Knowing a bit of Latin makes sense of many things that would otherwise seem completely arbitrary, such as why the symbols for gold, silver, and lead are Au, Ag, and Pb respectively.</p>
<p>Similarly, ed(1) is the Latin of Linux [1]. Many conventions in command line utilities follow conventions that go back to the ed(1) line editor. They may go back even further. Just as Latin didn&#8217;t come out of nowhere, neither did ed(1), but you can&#8217;t go back indefinitely. It&#8217;s convenient to start history somewhere, and this post will start with ed(1) just as much discussion of Western linguistics starts with Latin.</p>
<p>The following are features of ed(1) that live on in sed, awk, grep, vi, perl, bash, etc.</p>
<ol>
<li>Using slashes to delimit regular expressions</li>
<li>Using $ to indicate the end of a line or the end of a file</li>
<li>The pattern of specifying address + action or address range + action</li>
<li>Using regular expressions as address ranges</li>
<li>Using \1, \2, etc to refer to regex captures</li>
<li>Using &amp; to refer to the entire matched text</li>
<li>The g/regexp/command pattern</li>
<li>Using p for printing lines, as in g/re/p</li>
<li>The commands a, c, d, i, j, l, p, q, r, and w in vi</li>
<li>! for shell escape</li>
</ol>
<p>&nbsp;</p>
<p>[1] Because the name &#8220;ed&#8221; is so short, and looks so much like the name Ed, it&#8217;s convenient to use its full Unix name ed(1). The parenthesized number is used to disambiguate different things that have the same name, such as the user command kill(1) and the system call kill(2). There is no ed(2) or any other higher-numbered ed. The number is there to make the name stand out, not to disambiguate anything.</p>The post <a href="https://www.johndcook.com/blog/2026/06/04/the-latin-of-linux/">The Latin of Linux</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/06/04/the-latin-of-linux/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Integrating smooth periodic functions</title>
		<link>https://www.johndcook.com/blog/2026/06/04/integrating-smooth-periodic-functions/</link>
					<comments>https://www.johndcook.com/blog/2026/06/04/integrating-smooth-periodic-functions/#respond</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 17:18:40 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Integration]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247102</guid>

					<description><![CDATA[<p>Several posts lately have looked at the function f(x) = cos(sin(x) + x). This post will look at the function from a different angle. It&#8217;s a smooth function with period 2π. For reasons I wrote about here, this means that the trapezoid rule should find its integral very efficiently. In general, the error in the trapezoid [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/04/integrating-smooth-periodic-functions/">Integrating smooth periodic functions</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>Several posts lately have looked at the function</p>
<p style="padding-left: 40px;"><em>f</em>(<em>x</em>) = cos(sin(<em>x</em>) + <em>x</em>).</p>
<p>This post will look at the function from a different angle. It&#8217;s a smooth function with period 2π. For reasons I wrote about <a href="https://www.johndcook.com/blog/2017/03/01/numerically-integrating-periodic-functions/">here</a>, this means that the trapezoid rule should find its integral very efficiently.</p>
<p>In general, the error in the trapezoid rule is on the order of 1/<em>N</em>² where <em>N</em> is the number of integration points. To be more specific, the error in integrating a function <em>f</em> over [<em>a</em>, <em>b</em>] with <em>N</em> points is bounded by</p>
<p style="padding-left: 40px;">(<em>b</em> − <em>a</em>)³ <em>M</em> / 12<em>N</em>²</p>
<p>where <em>M</em> is the maximum absolute value of the second derivative of <em>f</em>. So in our case we should expect the error to be less than 82.67/<em>N</em>². In fact we do <em>much</em> better than that. The error does not decrease quadratically, as it does in general, but exponentially.</p>
<p><img fetchpriority="high" decoding="async" class="size-medium aligncenter" src="https://www.johndcook.com/periodic_gaussian.png" width="640" height="480" /></p>
<p>With just 16 integration points, we&#8217;ve reached the limit of floating point representation.</p>The post <a href="https://www.johndcook.com/blog/2026/06/04/integrating-smooth-periodic-functions/">Integrating smooth periodic functions</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/06/04/integrating-smooth-periodic-functions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Partitions over permutations</title>
		<link>https://www.johndcook.com/blog/2026/06/04/partitions-over-permutations/</link>
					<comments>https://www.johndcook.com/blog/2026/06/04/partitions-over-permutations/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 13:48:56 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Combinatorics]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247098</guid>

					<description><![CDATA[<p>I was thinking more about the cosine approximation to the Gaussian exp(−z²) ≈ (1 + cos(sin(z) + z))/2 that I wrote about last week. The two expressions above are close along the real axis but not along the imaginary axis. If z = iy, the right side grows much faster than the left, behaving like exp(exp(y)). This led to [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/04/partitions-over-permutations/">Partitions over permutations</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>I was thinking more about the cosine approximation to the Gaussian</p>
<p style="padding-left: 40px;">exp(−<em>z</em>²) ≈ (1 + cos(sin(<em>z</em>) + <em>z</em>))/2</p>
<p>that I wrote about <a href="https://www.johndcook.com/blog/2026/05/31/another-gaussian-approximation/">last</a> <a href="https://www.johndcook.com/blog/2026/06/01/not-just-taylor-series/">week</a>. The two expressions above are close along the real axis but not along the imaginary axis.</p>
<p>If <em>z</em> = <em>iy</em>, the right side grows much faster than the left, behaving like exp(exp(<em>y</em>)).</p>
<p>This led to me looking up the power series for the double exponential function exp(exp(<em>y</em>)). This is an interesting series because the coefficient of <em>x</em><sup><em>n</em></sup> is</p>
<p style="padding-left: 40px;"><em>e</em> <em>B</em><sub><em>n</em></sub> / <em>n</em>!</p>
<p>where <em>B</em><sub><em>n</em></sub> is the <em>n</em>th <a href="https://www.johndcook.com/blog/2018/06/05/bell-numbers/">Bell number</a>, which equals the number of ways to <strong>partition</strong> a set of <em>n</em> labeled items [1]. And of course <em>n</em>! is the number of ways to <strong>permute</strong> a set of <em>n</em> labeled items. So the <em>n</em>th coefficient in the power series for exp(exp(<em>y</em>)) is the ratio of the number of partitions to permutations for a set of <em>n</em> labeled things, multiplied by <em>e</em>.</p>
<p>The number of ways to partition a set of <em>n</em> things grows quickly as <em>n</em> increases, almost as quickly as the number of permutations, and so the series for the double exponential function converges very slowly.</p>
<h2>Computing</h2>
<p>SymPy has a function <code>bell</code> for computing Bell numbers, so you could compute the ratio of partitions to permutations as follows.</p>
<pre>from sympy import bell, factorial
f = lambda n: bell(n)/factorial(n)
</pre>
<p>This returns a number of type <code>sympy.core.numbers.Rational</code> and so the result is exact. You can cast it to float for convenience.</p>
<h2>Asymptotics</h2>
<p>If we look at only the terms in the asymptotic series for log <em>B</em><sub><em>n</em></sub> and log <em>n</em>! that grow with <em>n</em> we have</p>
<p style="padding-left: 40px;">log <em>B</em><sub><em>n</em></sub> ~ <em>n</em> log <em>n</em> − <em>n</em> log log <em>n</em><br />
log <em>n</em>! ~ <em>n</em> log <em>n</em> − ½ log <em>n</em></p>
<p>and so</p>
<p style="padding-left: 40px;">log( <em>B</em><sub><em>n</em></sub> / <em>n</em>! ) ~ ½ log <em>n − n</em> log log <em>n</em></p>
<p>There&#8217;s also an asymptotic series for log( <em>B</em><sub><em>n</em></sub> / <em>n</em>! ) involving the <a href="https://www.johndcook.com/blog/2021/04/23/lambert-w/">Lambert <em>W</em> function</a>:</p>
<p style="padding-left: 40px;">log( <em>B</em><sub><em>n</em></sub> / <em>n</em>! ) ~ <em>n</em>/<em>r</em> − 1 − <em>n</em> log <em>r</em></p>
<p>where <em>r </em>= <em>W</em>(<em>n</em>).</p>
<h2>Related posts</h2>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/2025/04/16/bell-numbers-2/">Mr. Bell and Bell numbers</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2021/11/14/estimating-the-number-of-integer-partitions/">Estimating partition numbers</a></li>
<li class="link"><a href="https://www.johndcook.com/TwelvefoldWay.pdf">Richard Stanley&#8217;s twelvefold way</a> (pdf)</li>
</ul>
<p>[1] It&#8217;s important that the items are labeled. Partition numbers are the number of partitions of an <em>unlabeled</em> set. Partition numbers are much smaller than Bell numbers.</p>The post <a href="https://www.johndcook.com/blog/2026/06/04/partitions-over-permutations/">Partitions over permutations</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/06/04/partitions-over-permutations/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Naively summing an alternating series</title>
		<link>https://www.johndcook.com/blog/2026/06/03/naive-sum/</link>
					<comments>https://www.johndcook.com/blog/2026/06/03/naive-sum/#respond</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Wed, 03 Jun 2026 15:13:24 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247085</guid>

					<description><![CDATA[<p>Suppose you run across the power series for the exponential function and decide to code it up. Good idea: you&#8217;ll probably learn something, though maybe not what you expect. Maybe you decide a tolerance of 10−12 is good enough, and so you sum the terms until the next term to add is below the tolerance. [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/03/naive-sum/">Naively summing an alternating series</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>Suppose you run across the power series for the exponential function and decide to code it up. Good idea: you&#8217;ll probably learn something, though maybe not what you expect.</p>
<p>Maybe you decide a tolerance of 10<sup>−12</sup> is good enough, and so you sum the terms until the next term to add is below the tolerance.</p>
<pre>from math import factorial, exp

def naive_exp(x):
    tolerance = 1e-12
    s = 0
    n = 0
    while True:
        delta = x**n / factorial(n)
        s += delta
        if abs(delta) &lt; tolerance:
            return s
        n += 1
</pre>
<p>You want to try your program out, so you compute <em>e</em> by calling the function at 1. If you compare this to calling <code>exp(1)</code> you find that you got all the digits correct.</p>
<p>Now you try computing exp(-20). Calling <code>naive_exp(-20)</code> gives</p>
<pre>    5.47893091802112e-10</pre>
<p>but calling <code>exp(-20)</code> gives</p>
<pre>    2.061153622438558e-09</pre>
<p>Don&#8217;t brush things like this as flukes or compiler bugs [1]. This is your golden opportunity to learn something.</p>
<p>Maybe you add a print statement to see the intermediate values of the sum stored in the variable <em>s</em>. If you do, you&#8217;ll see that the partial sums oscillate wildly before settling down.</p>
<p>Maybe that seems wrong, but then you look more carefully at the series. The <em>n</em>th term is <em>x</em><sup><em>n</em></sup>/<em>n</em>!. Since <em>x</em> is negative, the terms alternate in sign. And the absolute values of the term get bigger before they get smaller. When <em>x</em> = −20, each numerator is 20 times larger than the previous, and each denominator is <em>n</em> times larger than the previous. So the terms will get bigger until <em>n</em> &gt; 20. So the wild oscillations are real, not a bug.</p>
<p>The largest partial sum is 21822593.77927747 in absolute value. You know that exp(−20) is a very small number, so there&#8217;s going to have to be a lot of cancellation before the partial sums settle down to a small number. Maybe you&#8217;ve heard that cancellation is where numerical calculations lose precision. If not, now you know!</p>
<p>Look again at the largest partial sum. There are eight figures to the right of the decimal point. The code is printing out results to as much precision as it has, so the error at this point is on the order of 10<sup>−8</sup>. We&#8217;re trying to compute a number on the order of 10<sup>−9</sup>, and if <em>any</em> digits in our result are correct, it would be a coincidence.</p>
<p>If you go back and try your code on <em>x</em> = −22, the result is even worse, giving a negative result for a quantity that for theoretical reasons cannot be negative. But you can see why: you&#8217;re asking the code to compute a number that is closer to zero than the accuracy of the code.</p>
<p>Computers don&#8217;t represent numbers in base 10 internally, but the argument above is sufficient in this case. If you want to dig deeper, look into the <a href="https://www.johndcook.com/blog/2009/04/06/anatomy-of-a-floating-point-number/">anatomy of a floating point number</a>.</p>
<p>There is a simple way around the problem above, but discovering it sooner would short-circuit the learning process. You could calculate exp(−20) as 1/exp(20) and avoid all the cancellation because the series for exp(20) does not alternate.</p>
<p>&nbsp;</p>
<p>[1] Compilers do have bugs occasionally, but it&#8217;s orders of magnitude more likely that something is wrong with your code.</p>The post <a href="https://www.johndcook.com/blog/2026/06/03/naive-sum/">Naively summing an alternating series</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/06/03/naive-sum/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>It&#8217;s not just Taylor series</title>
		<link>https://www.johndcook.com/blog/2026/06/01/not-just-taylor-series/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Mon, 01 Jun 2026 12:42:12 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247071</guid>

					<description><![CDATA[<p>There is still active discussion on X about the approximation exp(−x²) ≈ (1 + cos(sin(x) + x))/2 and some are saying this can just be explained by Taylor series: the series for the two sides differ for the first time at the x6 term, so that&#8217;s why you get a good approximation. As I wrote [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/01/not-just-taylor-series/">It’s not just Taylor series</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>There is still active discussion on X about the approximation</p>
<p style="padding-left: 40px;">exp(−<em>x</em>²) ≈ (1 + cos(sin(<em>x</em>) + <em>x</em>))/2</p>
<p>and some are saying this can just be explained by Taylor series: the series for the two sides differ for the first time at the <em>x</em><sup>6</sup> term, so that&#8217;s why you get a good approximation. As I wrote <a href="https://www.johndcook.com/blog/2026/05/31/another-gaussian-approximation/">yesterday</a>, that&#8217;s only part of it.</p>
<p>If it were just about Taylor series you could use</p>
<p style="padding-left: 40px;">exp(−<em>x</em>²) ≈ 1 − <em>x</em>² + <em>x</em><sup>4</sup>/2</p>
<p>which also has error <em>O</em>(<em>x</em><sup>6</sup>). But this approximation is only good for fairly small <em>x</em>, say in [−0.5, 0.5], whereas the approximation at the top of the post is good over [−4, 4]. When <em>x</em> = 4, the error in the cosine approximation is 0.002579 but the error in the Taylor approximation is 113, five orders of magnitude larger.</p>
<p>If the accuracy of the cosine approximation were due to Taylor series, then we&#8217;d expect the function</p>
<p style="padding-left: 40px;">exp(−<em>x</em>²) − (1 + cos(sin(<em>x</em>) + <em>x</em>))/2</p>
<p>to be small not just over the interval [−4, 4] but over a disk of radius 4 in the complex plane. But it&#8217;s not. When <em>x</em> = 4<em>i</em> the function is on the order of 10<sup>13</sup>.</p>
<p>Both the cosine approximation and the Taylor approximation are good for small disks, say of radius 0.5. They&#8217;re both bad for much larger disks, and in fact the cosine approximation is worse.</p>
<p>&nbsp;</p>The post <a href="https://www.johndcook.com/blog/2026/06/01/not-just-taylor-series/">It’s not just Taylor series</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Subscribe by email</title>
		<link>https://www.johndcook.com/blog/2026/06/01/subscribe-by-email/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Mon, 01 Jun 2026 11:01:26 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247067</guid>

					<description><![CDATA[<p>Readers have subscribed to this blog via email almost from its beginning in 2008, but how they have subscribed has changed several times. I&#8217;ve used several services to provide email subscription that have come and gone. For the past two years I&#8217;ve been using Substack to send out emails announcing new blog posts. That has [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/06/01/subscribe-by-email/">Subscribe by email</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>Readers have subscribed to this blog via email almost from its beginning in 2008, but <em>how</em> they have subscribed has changed several times. I&#8217;ve used several services to provide email subscription that have come and gone.</p>
<p>For the past two years I&#8217;ve been using Substack to send out emails announcing new blog posts. That has worked out well. Substack delivers email reliably, and that&#8217;s all I wanted. I&#8217;m not active on Substack other than using it to an email. I give a brief introduction to the latest two or three blog posts in each email, and sometimes I include additional ideas that occurred to me later.</p>
<p>If you&#8217;d like to get blog post announcements and a little extra commentary via email, sign up for my free Substack newsletter <a href="https://johndcookconsulting.substack.com/">here</a>.</p>
<p>If you&#8217;d like to learn about new posts sooner, you can subscribe via <a href="https://www.johndcook.com/blog/rss-feed/">RSS</a> or follow me on <a href="https://www.johndcook.com/blog/twitter_page/">X</a> or <a href="https://mathstodon.xyz/@johndcook">Mastodon</a>.</p>The post <a href="https://www.johndcook.com/blog/2026/06/01/subscribe-by-email/">Subscribe by email</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Another Gaussian approximation</title>
		<link>https://www.johndcook.com/blog/2026/05/31/another-gaussian-approximation/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sun, 31 May 2026 16:15:49 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247061</guid>

					<description><![CDATA[<p>The function (1 + cos(x))/2 gives a fair approximation to the Gaussian density exp(−x²) You can make the approximation much better by raising it to a power. The function ((1 + cos(x))/2)4 gives a good lower bound and ((1 + cos(x))/2)3.5597 gives a good upper bound. More on that here. There are other ways of [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/31/another-gaussian-approximation/">Another Gaussian approximation</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The function</p>
<p style="padding-left: 40px;">(1 + cos(<em>x</em>))/2</p>
<p>gives a fair approximation to the Gaussian density</p>
<p style="padding-left: 40px;">exp(−<em>x</em>²)</p>
<p>You can make the approximation much better by raising it to a power. The function</p>
<p style="padding-left: 40px;">((1 + cos(<em>x</em>))/2)<sup>4</sup></p>
<p>gives a good lower bound and</p>
<p style="padding-left: 40px;">((1 + cos(<em>x</em>))/2)<sup>3.5597</sup></p>
<p>gives a good upper bound. More on that <a href="https://www.johndcook.com/blog/2023/07/05/normal-approximation/">here</a>.</p>
<p>There are other ways of improving the cosine approximation to the Gaussian. <a href="https://x.com/1strawberry_jam/status/2060730093404856692">Yesterday</a> I came across one I hadn&#8217;t seen before, adding a sin(<em>x</em>) term to <em>x</em>.</p>
<p style="padding-left: 40px;">(1 + cos(sin(<em>x</em>) + <em>x</em>))/2</p>
<p>This function matches the first few terms of the power series for exp(−<em>x</em>²) and has an error on the order of <em>x</em><sup>6</sup>/240. You can&#8217;t see the difference between the two functions in a plot for −4 ≤ <em>x</em> ≤ 4.</p>
<p style="text-align: center;">***</p>
<p>There&#8217;s a tension between the previous two statements. If the error in on the order of <em>x</em><sup>6</sup>/240 then we&#8217;d expect the error to be huge at <em>x</em> = 4. We have</p>
<p style="padding-left: 40px;">4<sup>6</sup>/240 = 17.07</p>
<p>and yet</p>
<p style="padding-left: 40px;">exp(−4²) − ((1 + cos(4 + sin(4)))/2) = −0.002579,</p>
<p>i.e. the error is between 3 and 4 orders of magnitude smaller than we might expect.</p>
<p>We have an alternating series, so the truncation error should be roughly equal to the first term after the truncation, right? No, the alternating series theorem doesn&#8217;t apply because the absolute values of the terms in the series are not decreasing yet for <em>x</em> = 4. The terms have to decrease eventually because the series has infinite radius of convergence, but they&#8217;re not decreasing at the 6th term; the terms will get much larger in absolute value before they get smaller.</p>
<p>The basic alternating series theorem gives only an upper bound on truncation error, but there are extensions that also give a lower bound. I wrote about these <a href="https://www.johndcook.com/blog/2026/03/17/alternating-series-remainder/">extensions</a> a few weeks ago. But they don&#8217;t apply here because the terms have not started decreasing in absolute value.</p>
<p><strong>Update</strong>: See further discussion in the post <a href="https://www.johndcook.com/blog/2026/06/01/not-just-taylor-series/">It&#8217;s not just Taylor series</a>.</p>The post <a href="https://www.johndcook.com/blog/2026/05/31/another-gaussian-approximation/">Another Gaussian approximation</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Spot checking polynomial identities</title>
		<link>https://www.johndcook.com/blog/2026/05/30/schwartz-zippel/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sat, 30 May 2026 21:06:49 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247056</guid>

					<description><![CDATA[<p>If a polynomial identity holds at a few random points, it&#8217;s very like true. We&#8217;ll make this statement more precise, but first let&#8217;s look at some applications. You may want to test an identity that naturally presents itself as a statement that two polynomials are equal. Or you might use something like the binomial coefficient [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/30/schwartz-zippel/">Spot checking polynomial identities</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>If a polynomial identity holds at a few random points, it&#8217;s very like true. We&#8217;ll make this statement more precise, but first let&#8217;s look at some applications.</p>
<p>You may want to test an identity that naturally presents itself as a statement that two polynomials are equal. Or you might use something like <a href="https://www.johndcook.com/blog/2012/07/21/binomial-coefficient-trick/">the binomial coefficient trick</a> to reframe a problem that isn&#8217;t obviously an identity about polynomials. And with algebraic circuits, you can reformulate a wide range of computations as polynomial identities; this is widely used in zero-knowledge proofs.</p>
<p>The theorem alluded to at the top of the post is the Schwartz-Zippel lemma. It is formulated in terms of the probability of a non-zero polynomial <em>P</em> evaluating to zero. To prove that two polynomials <em>Q</em><sub>1</sub> and <em>Q</em><sub>2</sub> are equal, you can show that</p>
<p style="padding-left: 40px;"><em>P</em> = Q<sub>1</sub>(<em>x</em>) − <em>Q</em><sub>2</sub>(<em>x</em>) = 0.</p>
<h2>Schwartz-Zippel lemma</h2>
<p>Let <em>F</em> be a (typically large) finite field and let <em>P</em> be a non-zero polynomial in <em>n</em> variables</p>
<p style="padding-left: 40px;"><em>P</em>(<em>x</em><sub>1</sub>, <em>x</em><sub>2</sub>, <em>x</em><sub>3</sub>, …, <em>x</em><sub><em>n</em></sub>)</p>
<p>of total degree <em>d</em>. If we choose the <em>x</em>&#8216;s randomly from <em>F</em> then the probability that <em>P</em> evaluates to zero is no more than <em>d</em>/|<em>F</em>|. [1]</p>
<p>If the total degree <em>d</em> is small relative to the size of the field, then the probability of <em>P</em> evaluating to zero is small. As long as <em>d</em> is less than |<em>F</em>|, you can evaluate the polynomial <em>k</em> times to make</p>
<p style="padding-left: 40px;">(<em>d</em> / |<em>F</em>|)<sup><em>k</em></sup></p>
<p>as small as you&#8217;d like. If <em>d</em> isn&#8217;t too large, and <em>F</em> is large, like the integers mod <em>p</em> = 2<sup>255</sup> − 19 used in cryptography, one polynomial evaluation might be enough to give convincing evidence that the polynomial is zero.</p>
<p>&nbsp;</p>
<p>[1] The Schwartz-Zippel lemma in its full generality applies to polynomials over an integral domain <em>R</em> with variables drawn from <em>S</em>, a finite subset of <em>R</em>. Here we&#8217;re setting <em>R</em> = <em>S</em> = <em>F</em>.</p>The post <a href="https://www.johndcook.com/blog/2026/05/30/schwartz-zippel/">Spot checking polynomial identities</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Online (one-pass) algorithms</title>
		<link>https://www.johndcook.com/blog/2026/05/29/online-one-pass-algorithms/</link>
					<comments>https://www.johndcook.com/blog/2026/05/29/online-one-pass-algorithms/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Fri, 29 May 2026 12:24:30 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[Math]]></category>
		<category><![CDATA[Probability and Statistics]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247047</guid>

					<description><![CDATA[<p>Canonical example The sample variance of a set of numbers is defined in terms of the sum of the squared distances from each point to the mean. So it would seem that you first need to calculate the mean, then go back and compute the squared differences from the mean. And yet sample variance can [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/29/online-one-pass-algorithms/">Online (one-pass) algorithms</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<h2>Canonical example</h2>
<p>The sample variance of a set of numbers is defined in terms of the sum of the squared distances from each point to the mean.</p>
<p><img decoding="async" class="aligncenter" style="background-color: white;" src="//www.johndcook.com/samplevariance.svg" alt="s^2 = \frac{1}{n-1}\sum_{i=1}^n (x_i -\bar{x})^2" width="200" /></p>
<p>So it would seem that you first need to calculate the mean, then go back and compute the squared differences from the mean. And yet sample variance can be computed in one pass through the data.</p>
<p>You&#8217;ll find two equivalent equations in statistics books: the one described above and another based on the sum of the data points and the sum of the data points squared.</p>
<p><img decoding="async" class="aligncenter" style="background-color: white;" src="//www.johndcook.com/variance2.svg" alt="s^2 = \frac{1}{n(n-1)}\left(n\sum_{i=1}^n x_i^2 -\left(\sum_{i=1}^n x_i\right)^2\right)" width="307" /></p>
<p>While this equation is theoretically correct, it is numerically unstable. Code that directly implements this equation can return a negative value for a quantity that is theoretically positive. I&#8217;ve seen this happen with real data, causing a program to crash when taking the square root of the variance to get the standard deviation.</p>
<p>However, there is an algorithm that computes mean and variance in one pass that is accurate and numerically stable. This algorithm was developed by B. P. Welford in 1962. I discuss Welford&#8217;s algorithm and give code for implementing it <a href="https://www.johndcook.com/blog/standard_deviation/">here</a>.</p>
<h2>Online algorithms</h2>
<p>Welford&#8217;s algorithm is known in computer science as an &#8220;online&#8221; algorithm. This term was coined well before the Internet. For example, see the paper [1] from 1965.</p>
<p>But of course now &#8220;online&#8221; means something else, and so the technical and colloquial uses of &#8220;online algorithm&#8221; have split. Technical literature uses the phrase to describe the kinds of algorithms in this post. Most people would take &#8220;online algorithm&#8221; to mean code that runs on a remote server. You may see &#8220;streaming algorithm&#8221; as a contemporary technical term, but I&#8217;d still search on &#8220;online algorithm&#8221; to find papers.</p>
<h2>Computing higher moments online</h2>
<p>Welford&#8217;s algorithm computes the first two moments, mean and variance, of a data set online. It is also possible to <a href="https://www.johndcook.com/blog/skewness_kurtosis/">compute skewness and kurtosis online</a>, as well as higher moments.</p>
<h2>Online regression</h2>
<p>Simple linear regression is closely related to calculating mean and variance, and it is possible to compute simple regression coefficients online. I have some old notes on this <a href="https://www.johndcook.com/running_regression.html">here</a>.</p>
<p>This post was motivated by an email asking me about multiple regression. It is also possible to compute multiple regression coefficients online, but I haven&#8217;t done this. I found a couple references, [2] and [3], but I have not read them. There is a simple procedure for two predictor variables but I believe things get a little more complicated with three or more predictors, requiring a recursive least squares algorithm.</p>
<h2>Related posts</h2>
<p>The notion of online algorithms is closely related to the notion of a fold in functional programming. Here are several posts on computing things with folds.</p>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/2016/06/09/insertion-sort-as-a-fold/">Insertion sort as a fold</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2016/07/14/kalman-filters-and-functional-programming/">Kalman filters as folds</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2016/06/08/computing-higher-moments-with-a-fold/">Computing higher moments with a fold</a></li>
</ul>
<p>[1] One-Tape, Off-Line Turing Machine Computations by F. C. Hennie. Information and Control. 8, 553-578 (1965). Available <a href="https://cse.iitkgp.ac.in/~goutam/toc/readingMaterial/one-tape-off-line.pdf">here</a>. In this paper Hennie writes &#8220;In an <em>on-line computation</em> the input data are supplied to the machine, one symbol at a time, at a special input terminal. … In an <em>off-line computation</em> all of the input symbols are written on one of the machine&#8217;s tapes prior to the start of the computation.</p>
<p>[2] Arthur Albert and Robert W. Sittler, “A Method for Computing Least Squares Estimators that Keep Up with the Data,” Journal of the Society for Industrial and Applied Mathematics, Series A: Control, 3(3), 384–417, 1965. DOI: 10.1137/0303026.</p>
<p>[3] Petre Stoica and Per Ashgren. Exact initialization of the recursive least-squares algorithm. Int. J. Adapt. Control Signal Process. 2002; 16:219&amp;ndashh;230.</p>The post <a href="https://www.johndcook.com/blog/2026/05/29/online-one-pass-algorithms/">Online (one-pass) algorithms</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/29/online-one-pass-algorithms/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Turning K-L divergence into a metric</title>
		<link>https://www.johndcook.com/blog/2026/05/27/jensen-shannon/</link>
					<comments>https://www.johndcook.com/blog/2026/05/27/jensen-shannon/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Thu, 28 May 2026 01:35:54 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Information theory]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247046</guid>

					<description><![CDATA[<p>Kullback-Leibler divergence Kullback-Leibler divergence is defined for two random variables X and Y by K-L divergence is non-negative, and it&#8217;s zero if and only if X and Y have the same distribution. But it is not a metric, for reasons explained here. For one thing, it&#8217;s not symmetric. Jeffreys divergence We can fix the symmetry problem by [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/27/jensen-shannon/">Turning K-L divergence into a metric</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<h2>Kullback-Leibler divergence</h2>
<p>Kullback-Leibler divergence is defined for two random variables <em>X</em> and <em>Y</em> by</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/kl_def2.svg" alt="D_{KL}(X || Y) = -\int f_X(x) \log \frac{f_Y(x)}{f_X(x)} \, dx" width="304" height="46" /></p>
<p>K-L divergence is non-negative, and it&#8217;s zero if and only if <em>X</em> and <em>Y</em> have the same distribution. But it is not a metric, for reasons explained <a href="https://www.johndcook.com/blog/2017/11/08/why-is-kullback-leibler-divergence-not-a-distance/">here</a>. For one thing, it&#8217;s not symmetric.</p>
<h2>Jeffreys divergence</h2>
<p>We can fix the symmetry problem by defining</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/jeffreys_divergence.svg" alt="J(X, Y) = D_{KL}(X || Y)  + D_{KL}(Y || X)" width="281" height="18" /></p>
<p>The <em>J</em> above stands for Jeffreys, for Harold Jeffreys. <em>J</em> is called either the symmetrized K-L divergence or Jeffreys&#8217; divergence. It&#8217;s still a divergence, not a distance.</p>
<p>A distance (metric) <em>d</em> has to have four properties:</p>
<ol>
<li><em>d</em>(<em>x</em>, <em>x</em>) = 0</li>
<li><em>d</em>(<em>x</em>, <em>y</em>) &gt; 0 if <em>x</em> ≠ <em>y</em></li>
<li><em>d</em>(<em>x</em>, <em>y</em>) = <em>d</em>(<em>y</em>, <em>x</em>)</li>
<li><em>d</em>(<em>x</em>, <em>z</em>) ≤ <em>d</em>(<em>x</em>, <em>y</em>) + <em>d</em>(<em>y</em>, <em>z</em>)</li>
</ol>
<p>K-L divergence satisfies the first two properties. Jeffreys&#8217; divergence satisfies the first three, but not the last one, the triangle inequality.</p>
<p>To show that <em>J</em> doesn&#8217;t satisfy the triangle inequality, let <em>X</em>, <em>Y</em>, and <em>Z</em> be Bernoulli random variables with <em>p</em> equal to 0.1, 0.2, and 0.3 respectively. Then the following Python code shows that the divergence from <em>X</em> to <em>Y</em>, plus the divergence from <em>Y</em> to <em>Z</em>, is less than the divergence from <em>X</em> to <em>Z</em>. This would be like saying you could get from LA to NYC faster by having a layover in Denver rather than taking a direct flight.</p>
<pre>from math import log

kl = lambda p, q: p*log(p/q) + (1-p)*log((1-p)/(1-q))
j  = lambda p, q: kl(p, q) + kl(q, p)

a = j(0.1, 0.2)
b = j(0.2, 0.3)
c = j(0.1, 0.3)
print(a + b, c)
</pre>
<p>This prints 0.135 and 0.270.</p>
<h2>Jensen-Shannon distance</h2>
<p>Jensen-Shannon distance turns K-L divergence into a metric as follows. First, define the random variable <em>M</em> to be the average of <em>X</em> and <em>Y</em>. Then average the K-L divergence from <em>M</em> to each of <em>X</em> and <em>Y</em>. This defines the Jensen-Shannon <strong>divergence</strong>. It&#8217;s still not a metric, but its square root is, which defines the Jensen-Shannon <strong>distance</strong><em>.</em></p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/jensen_shannon.svg" alt="\begin{align*} M &amp;= (X + Y)/2 \\ \text{JSD}(X, Y) &amp;= \tfrac{1}{2}D_{KL}(X || M) + \tfrac{1}{2}D_{KL}(Y || M) \\ d_{JS}(X, Y) &amp;= \sqrt{\text{JSD}(X, Y)} \end{align*}" width="331" height="85" /></p>
<p>The following code gives an example of Jensen-Shannon distance satisfying the triangle inequality.</p>
<pre>def d(p, q):
    m = 0.5*(p + q)
    jsd = 0.5*kl(p, m) + 0.5*kl(q, m) 
    return jsd**0.5

a = d(0.1, 0.2)
b = d(0.2, 0.3)
c = d(0.1, 0.3)
print(a + b, c)
</pre>
<p>This prints 0.1817 and 0.1801. Now a layover makes the trip longer.</p>The post <a href="https://www.johndcook.com/blog/2026/05/27/jensen-shannon/">Turning K-L divergence into a metric</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/27/jensen-shannon/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>The Meta logo and fitting Besace curves</title>
		<link>https://www.johndcook.com/blog/2026/05/27/the-meta-logo-and-fitting-besace-curves/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Wed, 27 May 2026 15:15:51 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247045</guid>

					<description><![CDATA[<p>I saw a post yesterday saying that the Meta logo is a Besace curve. A Besace curve has the implicit form and the parametric form where t ranges over [0, 2π]. So given a Besace curve, such as the Meta logo, how do you find the parameters a and b to fit the curve? We can rewrite [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/27/the-meta-logo-and-fitting-besace-curves/">The Meta logo and fitting Besace curves</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>I saw a <a href="https://mathstodon.xyz/@tpfto/116643531890145672">post</a> yesterday saying that the Meta logo is a Besace curve.</p>
<p><img loading="lazy" decoding="async" class="size-medium aligncenter" src="https://www.johndcook.com/meta_logo2.png" alt="Meta logo" width="318" height="88" /></p>
<p>A Besace curve has the implicit form</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace1.svg" alt="(x^2 - by)^2 = a^2(x^2 - y^2)" width="198" height="22" /></p>
<p>and the parametric form</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace2.svg" alt="\begin{align*} x &amp;= a\cos(t) - b \sin(t) \\ y &amp;= -\sin(t) x\end{align*}" width="177" height="47" /></p>
<p>where <em>t</em> ranges over [0, 2π].</p>
<p>So given a Besace curve, such as the Meta logo, how do you find the parameters <em>a</em> and <em>b</em> to fit the curve?</p>
<p>We can rewrite the parametric expression for <em>x</em> as a sine with a phase shift (see notes <a href="https://www.johndcook.com/blog/2024/11/12/sin-cos-phase/">here</a>)</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace3.svg" alt="x = A \sin(t + \phi)" width="128" height="18" /></p>
<p>where</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace4x.svg" alt="\begin{align*} A &amp;= \sqrt{a^2 + b^2} \\ \phi &amp;= -\arctan(a/b)\end{align*}" width="145" height="55" /></p>
<p>Also, we can rewrite the parametric expression for <em>y</em> as</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace5.svg" alt="\begin{align*} y &amp;= A \sin(t) \sin(t + \phi) \\ &amp;= \frac{A}{2} \left(\cos(\phi) - \cos(2t + \phi)\right) \\ \end{align*}" width="237" height="68" /></p>
<p>Now the extreme values of <em>x</em> and <em>y</em> are easier to see. The maximum value of <em>x</em> is <em>A</em> and the minimum value is −<em>A</em>. The maximum value of <em>y</em> is <em>A</em>(cos(φ) + 1)/2 and the minimum value is <em>A</em>(cos(φ) − 1)/2.</p>
<p>We can simplify the cosine of an arctangent (see <a href="https://www.johndcook.com/blog/2026/02/25/trig-of-inverse-trig/">here</a>) to find the height, i.e. the difference between the maximum and minimum <em>y</em> value, in terms of <em>a</em> and <em>b</em>.</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace6x.svg" alt="\begin{align*} \cos(\phi) &amp;= \cos(-\arctan(a/b)) \\ &amp;= \frac{1}{\sqrt{1 + (a/b)^2}} \\ &amp;= \frac{b}{\sqrt{a^2 + b^2}} \end{align*}" width="228" height="127" /></p>
<p>Then the height is given by</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace7y.svg" alt="\begin{align*} h &amp;= \frac{A}{2}(\cos(\phi) + 1) - \frac{A}{2}(\cos(\phi) - 1) \\ &amp;= A \cos(\phi) + A \\ &amp;= b + \sqrt{a^2 + b^2} \end{align*}" width="291" height="102" /></p>
<p>The width is given by</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace8.svg" alt="w = 2A = 2\sqrt{a^2 + b^2}" width="165" height="24" /></p>
<p>and so</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace9x.svg" alt="b = h - w/2" width="99" height="17" /></p>
<p>and</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/besace10x.svg" alt="a = \pm \sqrt{\frac{w^2}{4} - b^2}" width="131" height="48" /></p>
<p>Now the Meta logo is drawn with a thick line, and the line width isn&#8217;t constant. It&#8217;s a little fuzzy what the height and width of the middle of the curve are, but I estimated h = 120 and w = 200 from one image. This leads to <em>b</em> = 20 and <em>a</em> = 97.98.</p>
<p>The Mathematica code</p>
<pre>ParametricPlot[{a Cos[t] + 
   b Sin[t], -Sin[t] ( a Cos[t] + b Sin[t])}, {t, 0, 2 Pi}, 
 PlotStyle -&gt; Thickness[0.05]]
</pre>
<p>produces the following image.</p>
<p><img loading="lazy" decoding="async" class="size-medium aligncenter" src="https://www.johndcook.com/meta_mathematica.png" alt="Mathematica approximation of Meta logo" width="480" height="241" /></p>
<p>This is reminiscent of the Meta logo, but not a great match. I suspect the logo is not exactly a Besace curve. You could tinker with the <em>a</em> and <em>b</em> parameters and the aspect ratio to get a closer match. The logo may have been inspired by a Besace curve and then drawn by hand.</p>The post <a href="https://www.johndcook.com/blog/2026/05/27/the-meta-logo-and-fitting-besace-curves/">The Meta logo and fitting Besace curves</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Calculating the expected range of normal samples</title>
		<link>https://www.johndcook.com/blog/2026/05/26/calculating-expected-normal-range/</link>
					<comments>https://www.johndcook.com/blog/2026/05/26/calculating-expected-normal-range/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 26 May 2026 17:59:53 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[Probability]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247043</guid>

					<description><![CDATA[<p>The previous post looked at the expected IQ range in a jury of 12. This post will look more generally at computing the expected range of n samples from a N(0, 1) random variable. This will give the expected range in units of σ, i.e. multiply the results by σ if your σ isn&#8217;t 1. As mentioned [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/26/calculating-expected-normal-range/">Calculating the expected range of normal samples</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The <a href="https://www.johndcook.com/blog/2026/05/26/expected-iq-spread-on-a-jury/">previous post</a> looked at the expected IQ range in a jury of 12. This post will look more generally at computing the expected range of <em>n</em> samples from a <em>N</em>(0, 1) random variable. This will give the expected range in units of σ, i.e. multiply the results by σ if your σ isn&#8217;t 1.</p>
<p>As mentioned in the previous post, the expected range is given by</p>
<p><img loading="lazy" decoding="async" class="aligncenter" src="https://www.johndcook.com/normal_range.svg" alt="d_n = 2n \int_{-\infty}^\infty \Phi(x)^{n-1} \, x\,\phi(x) \, dx" width="249" height="45" /></p>
<p>where φ and Φ are the PDF and CDF of a standard normal. The integral can be calculated in closed form for <em>n</em> ≤ 5, but in general it requires numerical integration [1].</p>
<p>The following Python code can compute <em>d</em><sub><em>n</em></sub>.</p>
<pre>
from scipy.stats import norm
from scipy.integrate import quad
import numpy as np

def d(n):
    integrand = lambda x: x*norm.pdf(x)*norm.cdf(x)**(n-1)
    res, info = quad(integrand, -np.inf, np.inf)
    return 2*n*res
</pre>
<p>For large <em>n</em> we have the asymptotic approximation</p>
<p><img class='aligncenter' src='https://www.johndcook.com/normal_range2.svg' alt='d_n = 2 \Phi^{-1}\left( \frac{n \,–\, 0.375}{ n + 0.25} \right)' style='background-color:white' height='48' width='178' /></p>
<p>which we could implement in Python by</p>
<pre>
def approx(n):
    return 2*norm.ppf((n - 0.375)/(n + 0.25))
</pre>
<p>For very large <em>n</em> the asymptotic expression may be more accurate than the integral due to numerical integration error. </p>
<p>Here are a few example values.</p>
<pre>
|-----+-------|
|   n |   d_n |
|-----+-------|
|   2 | 1.128 |
|   3 | 1.693 |
|   5 | 2.326 |
|  10 | 3.078 |
|  12 | 3.258 |
|  23 | 3.858 |
|  50 | 4.498 |
| 100 | 5.015 |
|-----+-------|
</pre>
<p>[1] Order Statistics by H. A. David. John Wiley &amp; Sons. 1970.</p>The post <a href="https://www.johndcook.com/blog/2026/05/26/calculating-expected-normal-range/">Calculating the expected range of normal samples</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/26/calculating-expected-normal-range/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Expected IQ spread on a jury</title>
		<link>https://www.johndcook.com/blog/2026/05/26/expected-iq-spread-on-a-jury/</link>
					<comments>https://www.johndcook.com/blog/2026/05/26/expected-iq-spread-on-a-jury/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 26 May 2026 13:50:51 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Probability]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247041</guid>

					<description><![CDATA[<p>There&#8217;s been some discussion online lately about how a large difference in IQ makes it difficult for two people to communicate. There have been studies that confirm this effect. The difficulty is not insurmountable, but it takes deliberate effort to overcome. Someone dismissed this communication difficulty by pointing out that the expected difference in IQ [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/26/expected-iq-spread-on-a-jury/">Expected IQ spread on a jury</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>There&#8217;s been some discussion online lately about how a large difference in IQ makes it difficult for two people to communicate. There have been studies that confirm this effect. The difficulty is not insurmountable, but it takes deliberate effort to overcome.</p>
<p>Someone dismissed this communication difficulty by pointing out that the expected difference in IQ between two individuals is around 17, suggesting that most communication is between people who differ by more than one standard deviation in IQ. But this calculation assumes people are chosen at random, which they usually are not. People tend to live around and work around others of similar intelligence.</p>
<p>However, a jury <em>is</em> a random sample. It&#8217;s not a perfect random sample. For one thing, it starts with a random sample of people who are registered to vote, or who have a driver&#8217;s license, not all individuals. Furthermore, the pool of potential jurors is reduced to a jury through the process of <em>voir dire</em>, which is not random.</p>
<p>For this post I will make the simplifying assumption that a jury is a random sample from a population with normally distributed IQ with standard deviation σ = 15. The mean doesn&#8217;t matter here, but you could assume it&#8217;s 100 if you&#8217;d like.</p>
<p>By symmetry, the expected range of <em>n</em> samples from a normal random variable is twice the maximum. For <em>n</em> = 12 the range is about 3.26σ, which corresponds to nearly <strong>50 IQ points</strong>.</p>
<p>This suggests there&#8217;s usually a big spread of IQ on a jury. Even if IQ doesn&#8217;t measure intelligence, it measures <em>something</em>, and that something varies a lot over 12 people chosen at random [1].</p>
<h2>Related posts</h2>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/explaining-probability-to-a-jury/">Explaining probability to a jury</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/expert-testimony/">Expert testimony</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2016/01/12/seven-questions-a-statistician-could-answer-for-a-lawyer/">Seven questions a statistician can answer for a lawyer</a></li>
</ul>
<p>[1] In case you&#8217;re interested in the technical details, the expected range of <em>n</em> samples from a standard normal random variable is given by</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/normal_range.svg" alt="d_n = 2n \int_{-\infty}^\infty \Phi(x)^{n-1} \, x\,\phi(x) \, dx" width="249" height="45" /></p>
<p>where φ and Φ are the PDF and CDF of a standard normal. Multiply this by σ to get the range of a normal random variable with standard deviation σ. As for how to calculate <em>d</em><sub><em>n</em></sub>, see the <a href="https://www.johndcook.com/blog/2026/05/26/calculating-expected-normal-range/">next post</a>.</p>The post <a href="https://www.johndcook.com/blog/2026/05/26/expected-iq-spread-on-a-jury/">Expected IQ spread on a jury</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/26/expected-iq-spread-on-a-jury/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>Hilbert transform as an infinite matrix</title>
		<link>https://www.johndcook.com/blog/2026/05/23/hilbert-transform-as-an-infinite-matrix/</link>
					<comments>https://www.johndcook.com/blog/2026/05/23/hilbert-transform-as-an-infinite-matrix/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sat, 23 May 2026 15:09:34 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Fourier analysis]]></category>
		<category><![CDATA[Linear algebra]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247039</guid>

					<description><![CDATA[<p>The previous post linked to a post I wrote a few years ago about the Hilbert transform and Fourier series. That post says that if the Fourier series of a function is then the Fourier series of its Hilbert transform is When I looked back at that post I thought about how if you thought [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/23/hilbert-transform-as-an-infinite-matrix/">Hilbert transform as an infinite matrix</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The previous post linked to a post I wrote a few years ago about the <a href="https://www.johndcook.com/blog/2022/04/13/hilbert-fourier/">Hilbert transform and Fourier series</a>. That post says that if the Fourier series of a function is</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" style="background-color: white;" src="https://www.johndcook.com/hilbert_fourier1.svg" alt="f(t) = \sum_{n=1}^\infty \left\{ a_n \sin(nt) + b_n\cos(nt) \right\}" width="268" height="49" /></p>
<p>then the Fourier series of its Hilbert transform is</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" style="background-color: white;" src="https://www.johndcook.com/hilbert_fourier2.svg" alt="f_H(x) = \sum_{n=1}^\infty \left\{ -b_n \sin(nx) + a_n\cos(nx) \right\}" width="303" height="49" /></p>
<p>When I looked back at that post I thought about how if you thought of the Fourier coefficients as elements of an infinite vector then the Hilbert transform can be represented as multiplying by an infinite block matrix.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" style="background-color: white;" src="https://www.johndcook.com/hilbert_transform_matrix.svg" alt="\left[ \begin{array}{cc|cc|cc|c} 0 &amp; -1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; \cdots \\ 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; \cdots \\ \hline 0 &amp; 0 &amp; 0 &amp; -1 &amp; 0 &amp; 0 &amp; \cdots \\ 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; \cdots \\ \hline 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; -1 &amp; \cdots \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; \cdots \\ \hline \vdots &amp; \vdots &amp; \vdots &amp; \vdots &amp; \vdots &amp; \vdots &amp; \ddots \end{array} \right] \left[ \begin{array}{c} a_1 \\ b_1 \\ \hline a_2 \\ b_2 \\ \hline a_3 \\ b_3 \\ \hline \vdots \end{array} \right]" width="363" height="183" /></p>
<p>I rarely see infinite matrices except in older math books. Apparently they were more fashionable a few decades ago than they are now. I suppose the notation falls between two stools, too concrete for some tastes and not concrete enough for others. The former folks would prefer something like <em>H</em> and the latter would prefer the sum above.</p>The post <a href="https://www.johndcook.com/blog/2026/05/23/hilbert-transform-as-an-infinite-matrix/">Hilbert transform as an infinite matrix</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/23/hilbert-transform-as-an-infinite-matrix/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Real and imaginary parts</title>
		<link>https://www.johndcook.com/blog/2026/05/23/real-and-imaginary-parts/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sat, 23 May 2026 13:57:21 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<category><![CDATA[Differential equations]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247038</guid>

					<description><![CDATA[<p>The previous post announced some notes I wrote up based on an article by Henry Baker implementing functions of a complex variable in terms of functions of a real variable. That is, it finds functions u(x, y) and v(x, y) such that f(x + iy) = u(x, y) + i v(x, y) where x, y, u, and v are [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/23/real-and-imaginary-parts/">Real and imaginary parts</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The previous post announced some <a href="https://www.johndcook.com/complex_real.html">notes</a> I wrote up based on an article by Henry Baker implementing functions of a complex variable in terms of functions of a real variable. That is, it finds functions <em>u</em>(<em>x</em>, <em>y</em>) and <em>v</em>(<em>x</em>, <em>y</em>) such that</p>
<p style="padding-left: 40px;"><em>f</em>(<em>x</em> + <em>iy</em>) = <em>u</em>(<em>x</em>, <em>y</em>) + <em>i</em> <em>v</em>(<em>x</em>, <em>y</em>)</p>
<p>where <em>x</em>, <em>y</em>, <em>u</em>, and <em>v</em> are all real-valued. Not only that, but if <em>f</em> is an elementary function, so are <em>u</em> and <em>v</em>. Here &#8220;elementary&#8221; has a technical meaning, but essentially it means functions that you could evaluate on a scientific calculator. A couple of the functions might be unfamiliar, such as sgn and atan2, but there are no functions like the gamma function that are defined in terms of integrals.</p>
<p>One application of Baker&#8217;s equations would be to bootstrap a math library that doesn&#8217;t support complex numbers into one that does. But the equations could be useful in pure math when you&#8217;d like to have a convenient expression for the real or imaginary part of a function.</p>
<p>The real and imaginary parts of a complex analytic function are harmonic functions. So the functions on the right hand side of Baker&#8217;s equations satisfy <a href="https://www.johndcook.com/blog/2022/11/22/dirichlet/">Laplace&#8217;s equation</a>:</p>
<p style="padding-left: 40px;"><em>u</em><sub><em>xx</em></sub> + <em>u</em><sub><em>yy</em></sub> = 0</p>
<p>and</p>
<p style="padding-left: 40px;"><em>v</em><sub><em>xx</em></sub> + <em>v</em><sub><em>yy</em></sub> = 0.</p>
<p>Furthermore, the functions <em>u</em> and <em>v</em> form harmonic conjugate pairs, meaning each is the <a href="https://www.johndcook.com/blog/2022/04/13/hilbert-fourier/">Hilbert transform</a> of the other.</p>The post <a href="https://www.johndcook.com/blog/2026/05/23/real-and-imaginary-parts/">Real and imaginary parts</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Building complex functions out of real parts</title>
		<link>https://www.johndcook.com/blog/2026/05/22/complex-functions-real-parts/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sat, 23 May 2026 03:24:10 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247036</guid>

					<description><![CDATA[<p>A couple months ago I wrote about how to compute the sine and cosine of a complex number using only real functions of real variables using the equations You can do something analogous for all the elementary functions, though some of the equations are quite a bit more complicated than the ones above. See the [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/22/complex-functions-real-parts/">Building complex functions out of real parts</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p><a href="https://www.johndcook.com/blog/2026/03/27/complex-argument/">A couple months ago</a> I wrote about how to compute the sine and cosine of a complex number using only real functions of real variables using the equations</p>
<p><img loading="lazy" decoding="async" class="aligncenter" src="https://www.johndcook.com/complex_sincos1.svg" alt="\begin{align*} \sin(x + iy) &amp;= \sin x \cosh y + i \cos x \sinh y \\ \cos(x + iy) &amp;= \cos x \cosh y - i \sin x \sinh y \\ \end{align*}" width="324" height="47" /></p>
<p>You can do something analogous for all the elementary functions, though some of the equations are quite a bit more complicated than the ones above. See the equations <a href="https://www.johndcook.com/complex_real.html">here</a>.</p>
<p>The equations come from a paper by Henry G. Baker, cited in the linked page. I wrote up Baker&#8217;s equations in LaTeX, then used ChatGPT to generate Python code from the LaTeX to numerically verify the equations and my typesetting of them. This caught a few typos on my part.</p>
<p>The test code evaluated the equations at points from each quadrant. All matched NumPy, implying that Baker and NumPy use the same branch cuts on inverse functions.</p>
<p style="text-align: center;">***</p>
<p>This post is part of a thread that has gone on for a few days. Maybe it&#8217;s the last post in the thread; we&#8217;ll see.</p>
<p>It all started with a post on <a href="https://www.johndcook.com/blog/2026/05/19/zagiers-equation/">Markov&#8217;s equation</a></p>
<p style="padding-left: 40px;"><em>x</em>² + <em>y</em>² + <em>z</em>² = 3 <em>xyz</em></p>
<p>and an approximation to the equation that has a closed-form solution. That led to the identity</p>
<p style="padding-left: 40px;">cosh( arccosh(<em>a</em>) + arccosh(<em>b</em>) ) = <em>ab</em> + √(<em>a</em>² − 1) √(<em>b</em>² − 1).</p>
<p>The approximation to Markov&#8217;s equation only needed the identity to be valid for real <em>a</em> and <em>b</em> greater than 1. But when I <a href="https://www.johndcook.com/blog/2026/05/19/closer-look-at-an-identity/">looked closer</a> at the identity I found several complications with branch cuts. The identity doesn&#8217;t hold everywhere using the principle branch of the square root function. But if you <a href="https://www.johndcook.com/blog/2026/05/19/square-root-of-x-squared-minus-one/">define</a> √(<em>z</em>² − 1) to have a branch cut along [−1, 1] then the equation holds everywhere in the complex plane. And that led to my writing up some <a href="https://www.johndcook.com/inverse.html">notes</a> on how to define all the elementary inverse functions in terms of log.</p>
<p>Someone reading these posts suggested I look at a paper that mentioned &#8220;couth&#8221; and &#8220;uncouth&#8221; function pairs, which led to <a href="https://www.johndcook.com/blog/2026/05/21/couth-and-uncouth-function-pairs/">this post</a> and its <a href="https://www.johndcook.com/blog/2026/05/21/circular-hyperbolic-rotations/">warm up</a>.</p>
<p>I find all this interesting because it&#8217;s an advanced perspective on a questions that are latent in an intro calculus class. What <em>exactly</em> do functions like arccos mean and why where they defined as they were? These are fairly deep and interesting questions that are swept under the rug, and swept there for good reason. A calculus class has to cover an enormous amount of material and there&#8217;s no time to dwell on fine points. Some of my favorite posts look back leisurely on things that go by in a blur when you&#8217;re a student.</p>The post <a href="https://www.johndcook.com/blog/2026/05/22/complex-functions-real-parts/">Building complex functions out of real parts</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Couth and uncouth function pairs</title>
		<link>https://www.johndcook.com/blog/2026/05/21/couth-and-uncouth-function-pairs/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Thu, 21 May 2026 17:31:13 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<category><![CDATA[Special functions]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247031</guid>

					<description><![CDATA[<p>&#8220;You can&#8217;t always get what you want. But sometimes you get what you need.&#8221; — The Rolling Stones Circular functions and hyperbolic functions aren&#8217;t invertible, but we invert them anyway. These functions map many points in the domain to each point in the range, and we invert them by mapping a point in the range [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/21/couth-and-uncouth-function-pairs/">Couth and uncouth function pairs</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>&#8220;You can&#8217;t always get what you want. But sometimes you get what you need.&#8221; — The Rolling Stones</p>
<p>Circular functions and hyperbolic functions aren&#8217;t invertible, but we invert them anyway. These functions map many points in the domain to each point in the range, and we invert them by mapping a point in the range back to <em>some</em> point in the domain. Often this works as expected, but sometimes it doesn&#8217;t.</p>
<p>In the <a href="https://www.johndcook.com/blog/2026/05/21/circular-hyperbolic-rotations/">previous post</a> I said</p>
<blockquote><p>You can relate each trig function &#8220;foo&#8221; with its hyperbolic counterpart &#8220;fooh&#8221; by applying one of the functions to <em>iz</em> then multiplying by a constant <em>c</em> that depends on foo: <em>c</em> = <em>i</em> for sin and tan, <em>c</em> = 1 for cos and sec, and <em>c</em> = −<em>i</em> for csc and cot.</p></blockquote>
<p>In symbols,</p>
<p style="padding-left: 40px;"><em>c</em> foo(<em>z</em>) = fooh(<em>iz</em>).</p>
<p>Let&#8217;s suppose foo and fooh are invertible, ignoring any complications, and solve foo(<em>z</em>) = <em>w</em> for <em>z</em>. We get</p>
<p style="padding-left: 40px;"><em>i</em> foo<sup>−1</sup>(<em>w</em>) = fooh<sup>−1</sup>(<em>cw</em>)</p>
<p>or using &#8220;arc&#8221; nomenclature for inverse functions</p>
<p style="padding-left: 40px;"><em>i</em> arcfoo(<em>w</em>) = arcfooh(<em>cw</em>).</p>
<p>When the naive calculation above holds, except possibly at a finite number of points, we say the pair (foo, fooh) is <strong>couth</strong>. Otherwise we say the pair is <strong>uncouth</strong>. These term were coined by Robert Corless and his coauthors in their paper [1].</p>
<p>Whether the pair (foo, fooh) is couth depends not only on foo and fooh, but also on the details of how arcfoo and arcfooh are defined.</p>
<p>In Python&#8217;s NumPy library, the pairs (sin, sinh) and (tan, tanh) are couth, but the pair (cos, cosh) is uncouth.</p>
<p>Numpy doesn&#8217;t define the reciprocal functions sec, sech, csc, csch, cot, and coth. I used to find that annoying, but I&#8217;m beginning to think that was wise. These functions cause problems. For example, there may be two reasonable ways to define these functions, one of which forms a couth pair and one of which forms an uncouth pair.</p>
<p>For example, how should you define cot and coth? There would be no disagreement over the definition</p>
<pre>cot = lambda x: 1/tan(x)</pre>
<p>but there are at least two definitions of inverse coth that you&#8217;ll find in practice:</p>
<pre>
arccot = lambda z: 0.5*pi - arctan(z)
arccot = lambda z: arctan(1/z).
</pre>
<p>Both definitions have their advantages, but the former is uncouth while the latter is couth. You can verify that both definitions are the same at <em>z</em> = 1 but not at <em>z</em> = &minus;1.</p>
<p>With the following definitions, the pairs (cos, cosh) and (sec, sech) are uncouth and the rest are couth.</p>
<pre>
from numpy import *

csc     = lambda x: 1/sin(x)
sec     = lambda x: 1/cos(x)
cot     = lambda x: 1/tan(x)
csch    = lambda x: 1/sinh(x)
sech    = lambda x: 1/cosh(x)
coth    = lambda x: 1/tanh(x)

arccot  = lambda z: arctan(1/z)
arcsec  = lambda z: arccos(1/z)
arccsc  = lambda z: arcsin(1/z)
arccoth = lambda z: arctanh(1/z)
arcsech = lambda z: arccosh(1/z)
arccsch = lambda z: arcsinh(1/z)
</pre>
<p>[1] &#8220;According to Abramowitz and Stegun&#8221; or arccoth needn&#8217;t be uncouth. Robert M. Corless <em>et al</em>. ACM SIGSAM Bulletin, Volume 34, Issue 2, pp 58 &#8211; 65 https://doi.org/10.1145/362001.362023</p>The post <a href="https://www.johndcook.com/blog/2026/05/21/couth-and-uncouth-function-pairs/">Couth and uncouth function pairs</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Circular and hyperbolic functions differ by rotations</title>
		<link>https://www.johndcook.com/blog/2026/05/21/circular-hyperbolic-rotations/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Thu, 21 May 2026 15:53:56 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247032</guid>

					<description><![CDATA[<p>The difference between a circular function and a hyperbolic function is a rotation or two. For example, cosh(z) = cos(iz). You can read that as saying that to find the hyperbolic cosine of z, first you rotate z a quarter turn to the left (i.e. multiply by i) and then take the cosine. For another example, [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/21/circular-hyperbolic-rotations/">Circular and hyperbolic functions differ by rotations</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The difference between a circular function and a hyperbolic function is a rotation or two.</p>
<p>For example, cosh(<em>z</em>) = cos(<em>iz</em>). You can read that as saying that to find the hyperbolic cosine of <em>z</em>, first you rotate <em>z</em> a quarter turn to the left (i.e. multiply by <em>i</em>) and then take the cosine.</p>
<p>For another example, sinh(<em>z</em>) = −<em>i</em> sin(<em>iz</em>). This says that you can calculate the hyperbolic sine of <em>z</em> by rotating <em>z</em> to the left, taking the sine, and then rotating to the right.</p>
<p>You can relate each trig function &#8220;foo&#8221; with its hyperbolic counterpart &#8220;fooh&#8221; by applying one of the functions to <em>iz</em> then multiplying by a constant <em>c</em> that depends on foo: <em>c</em> = <em>i</em> for sin and tan, <em>c</em> = 1 for cos and sec, and <em>c</em> = −<em>i</em> for csc and cot.</p>
<p>Note that if the constant for foo is <em>c</em>, the constant for 1/foo is 1/<em>c</em>. So, for example, the constant for tan is <em>i</em> and the constant for cot is 1/<em>i</em> = −<em>i</em>.</p>
<p>We have four groups of equations, depending on whether the left side of the equation is foo(<em>iz</em>), fooh(<em>iz</em>), foo(<em>z</em>), or fooh(<em>z</em>).</p>
<p>This post was written as a warm-up for the <a href="https://www.johndcook.com/blog/2026/05/21/couth-and-uncouth-function-pairs/">next post</a> on couth and uncouth function pairs.</p>
<h2>foo(iz)</h2>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/fooiz2.png" width="320" height="291" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/fooiz.svg" alt="\begin{align*} \sin(iz) &amp; = \phantom{-}i\sinh(z) \\ \cos(iz) &amp; = \phantom{-i}\cosh(z) \\ \tan(iz) &amp; = \phantom{-}i\tanh(z) \\ \csc(iz) &amp; = -i\text{csch}(z) \\ \sec(iz) &amp; = \phantom{-i}\text{sech}(z) \\ \cot(iz) &amp; = -i\coth(z) \\ \end{align*}" width="163" height="163" /></p>
<h2>fooh(iz)</h2>
<p><img loading="lazy" decoding="async" class="size-medium aligncenter" src="https://www.johndcook.com/foohiz2.png" width="320" height="291" /><br />
<img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/foohiz.svg" alt="\begin{align*} \sinh(iz) &amp; = \phantom{-}i\sin(z) \\ \cosh(iz) &amp; = \phantom{-i}\cos(z) \\ \tanh(iz) &amp; = \phantom{-}i\tan(z) \\ \text{csch}(iz) &amp; = -i\csc(z) \\ \text{sech}(iz) &amp; = \phantom{-i}\sec(z) \\ \coth(iz) &amp; = -i\cot(z) \\ \end{align*}" width="163" height="163" /></p>
<h2>foo(z)</h2>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/fooz2.png" width="320" height="297" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/fooz.svg" alt="\begin{align*} \sin(z) &amp; = -i\sinh(iz) \\ \cos(z) &amp; = \phantom{-i}\cosh(iz) \\ \tan(z) &amp; = -i\tanh(iz) \\ \csc(z) &amp; = \phantom{-}i\text{csch}(iz) \\ \sec(z) &amp; = \phantom{-i}\text{sech}(iz) \\ \cot(z) &amp; = \phantom{-}i\coth(iz) \\ \end{align*}" width="163" height="163" /></p>
<h2>fooh(z)</h2>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/foohz2.png" width="320" height="296" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/fooh.svg" alt="\begin{align*} \sinh(z) &amp; = -i\sin(iz) \\ \cosh(z) &amp; = \phantom{-i}\cos(iz) \\ \tanh(z) &amp; = -i\tan(iz) \\ \text{csch}(z) &amp; = \phantom{-}i\csc(iz) \\ \text{sech}(z) &amp; = \phantom{-i}\sec(iz) \\ \coth(z) &amp; = \phantom{-}i\cot(iz) \\ \end{align*} " width="163" height="163" /></p>The post <a href="https://www.johndcook.com/blog/2026/05/21/circular-hyperbolic-rotations/">Circular and hyperbolic functions differ by rotations</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Square root of x² − 1</title>
		<link>https://www.johndcook.com/blog/2026/05/19/square-root-of-x-squared-minus-one/</link>
					<comments>https://www.johndcook.com/blog/2026/05/19/square-root-of-x-squared-minus-one/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Wed, 20 May 2026 00:49:50 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247029</guid>

					<description><![CDATA[<p>How should we define √(z² − 1)? Well, you could square z, subtract 1, and take the square root. What else would you do?! The question turns out to be more subtle than it looks. When x is a non-negative real number, √x is defined to be the non-negative real number whose square is x. When x is [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/19/square-root-of-x-squared-minus-one/">Square root of x² − 1</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>How should we define √(<em>z</em>² − 1)? Well, you could square <em>z</em>, subtract 1, and take the square root. What else would you do?!</p>
<p>The question turns out to be more subtle than it looks.</p>
<p>When <em>x</em> is a non-negative real number, √<em>x</em> is defined to be the non-negative real number whose square is <em>x</em>. When <em>x</em> is a complex number √<em>x</em> is defined to be <strong>a</strong> function that extends √<em>x</em> from the real line to the complex plane by analytic continuation. But we can&#8217;t extend √<em>x</em> as an analytic function to the entire complex plane ℂ. We have to choose to make a &#8220;cut&#8221; somewhere, and the conventional choice is to make a cut along the negative real axis.</p>
<h2>Using the principal branch</h2>
<p>The &#8220;principal branch&#8221; of the square root function is defined to be the unique function that analytically extends √<em>x</em> from the positive reals to ℂ \ (−∞, 0].</p>
<p>Assume for now that by √<em>x</em> we mean the principal branch of the square root function. Now what does √(<em>z</em>² − 1) mean? It <em>could</em> mean just what we said at the top of the post: we square <em>z</em>, subtract 1, and apply the (principal branch of the) square root function. If we do that, we must exclude those values of <em>z</em> such that (<em>z</em>² − 1) is negative. This means we have to cut out the imaginary axis and the interval [−1, 1].</p>
<p>This is what Mathematica will do when asked to evaluate <code>Sqrt[z^2 - 1]</code>. The command</p>
<pre>ComplexPlot[Sqrt[z^2 - 1], {z, -2 - 2 I, 2 + 2 I}]</pre>
<p>makes the branch cuts clear by abrupt changes in color.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/sqrt_branch1.png" width="480" height="483" /></p>
<h2>A different approach</h2>
<p>Now let&#8217;s take a different approach. Consider the function √(<em>z</em>² − 1) as a whole. Do not think of it procedurally as above, first squaring <em>z</em> etc. Instead, think of a it as a black box that takes in <em>z</em> and returns a complex number whose square is <em>z</em>² − 1.</p>
<p>This function has an obvious definition for <em>z</em> &gt; 1. And we can extend this function, via analytic continuation, to more of the complex plane. We can do this <em>directly</em>, not by extending the square root function. But as before, we cannot extend the function analytically to all of ℂ. We have to cut something out. A common choice is to cut out [−1, 1]. This eliminates the need for a branch cut along the imaginary axis.</p>
<p>The function</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cosh_sum5.svg" alt="f(z) = \exp\left( \tfrac{1}{2}\left( \log(z - 1) + \log(z + 1) \right) \right)" width="320" height="36" /></p>
<p>extends √(<em>z</em>² − 1) the way we want [1].</p>
<p>The Mathematica code</p>
<pre>ComplexPlot[Exp[(1/2) (Log[z - 1 ] + Log[z + 1])], {z, -2 - 2 I, 2 + 2 I}]</pre>
<p>shows that our function is now continuous across the imaginary axis, though there&#8217;s still a discontinuity as you cross [−1, 1].</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/sqrt_branch2.png" width="480" height="483" /></p>
<p>We used this analytic extension of √(<em>z</em>² − 1) in the <a href="https://www.johndcook.com/blog/2026/05/19/closer-look-at-an-identity/">previous post</a> to eliminate branch cuts in an identity.</p>
<h2>Related posts</h2>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/2026/03/09/trig-composition-table/">Trig composition table</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2026/03/10/sinh-arccosh/">sinh( arccosh(x) )</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2025/03/03/mathematica-contour-plot/">Duplicating a hand-drawn plot</a></li>
</ul>
<p>[1] The principal branch of the logarithm has a cut along the negative real axis. Why does our square root function, defined using log, not have a branch cut along the negative axis?</p>
<p>First of all, the log function, and Mathematica&#8217;s implementation of it <code>Log[]</code>, isn&#8217;t undefined on (−∞, 1), it just isn&#8217;t continuous there. The function still has a value. By convention, the value is taken to be the limit of log(<em>z</em>) approaching <em>z</em> from above, i.e. from the 2nd quadrant.</p>
<p>Second, the value of (log(<em>z</em> &#8211; 1) + log(<em>z</em> + 1))/2 differs by a factor of 2π<em>i</em> when approaching a value <em>z</em> &lt; −1 from above versus from below. This factor goes away when taking the exponential. So our function <em>f</em>(<em>z</em>) is analytic across (−∞, 1) even though the log functions in its definition are not.</p>The post <a href="https://www.johndcook.com/blog/2026/05/19/square-root-of-x-squared-minus-one/">Square root of x² − 1</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/19/square-root-of-x-squared-minus-one/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Closer look at an identity</title>
		<link>https://www.johndcook.com/blog/2026/05/19/closer-look-at-an-identity/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 19 May 2026 23:37:31 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Complex analysis]]></category>
		<category><![CDATA[Mathematica]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247027</guid>

					<description><![CDATA[<p>The previous post derived the identity and said in a footnote that the identity holds at least for x &#62; 1 and y &#62; 1. That&#8217;s true, but let&#8217;s see why the footnote is necessary. Let&#8217;s have Mathematica plot The plot will be 0 where the identity above holds. The plot is indeed flat for x &#62; 1 [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/19/closer-look-at-an-identity/">Closer look at an identity</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The previous post derived the identity</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cosh_sum1.svg" alt="\cosh\Big( \operatorname{arccosh}(x) + \operatorname{arccosh}(y)\Big) = xy + \sqrt{x^2 -1} \sqrt{y^2 -1}" width="452" height="38" /></p>
<p>and said in a footnote that the identity holds at least for <em>x</em> &gt; 1 and <em>y</em> &gt; 1. That&#8217;s true, but let&#8217;s see why the footnote is necessary.</p>
<p>Let&#8217;s have Mathematica plot</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cosh_sum2.svg" alt="\left| \cosh\Big( \operatorname{arccosh}(x) + \operatorname{arccosh}(y)\Big) - xy - \sqrt{x^2 -1} \sqrt{y^2 -1} \right|" width="461" height="48" /></p>
<p>The plot will be 0 where the identity above holds.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/coshacosh1.png" width="480" height="399" /></p>
<p>The plot is indeed flat for <em>x</em> &gt; 1 and <em>y</em> &gt; 1, and more, but not everywhere.</p>
<p>If we combine the two square roots</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cosh_sum3.svg" alt="\left| \cosh\Big( \operatorname{arccosh}(x) + \operatorname{arccosh}(y)\Big) - xy - \sqrt{(x^2 -1) (y^2 -1)} \right|" width="479" height="48" /></p>
<p>and plot again we still get a valid identity for <em>x</em> &gt; 1 and <em>y</em> &gt; 1, but the plot changes.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/coshacosh2.png" width="480" height="399" /></p>
<p>This is because √<em>a</em> √<em>b</em> does not necessarily equal √(<em>ab</em>) when the arguments may be negative.</p>
<p>The square root function and the arccosh function are not naturally single-valued functions. They require branch cuts to force them to be single-valued, and the two functions require <em>different</em> branch cuts. I go into this in some detail <a href="https://www.johndcook.com/blog/2026/03/10/sinh-arccosh/">here</a>.</p>
<p>There is a way to reformulate our identity so that it holds everywhere. If we replace</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cosh_sum4.svg" alt="\sqrt{z^2 - 1}" width="63" height="24" /></p>
<p>with</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/cosh_sum5.svg" alt="f(z) = \exp\left( \tfrac{1}{2}\left( \log(z - 1) + \log(z + 1) \right) \right)" width="320" height="36" /></p>
<p>which is equivalent for <em>z</em> &gt; 1, the corresponding identity holds everywhere.</p>
<p>We can verify this with the following Mathematica code.</p>
<pre>f[z_] := Exp[(1/2) (Log[z - 1 ] + Log[z + 1])]
FullSimplify[Cosh[ArcCosh[x] + ArcCosh[y]] - x y - f[x] f[y]]
</pre>
<p>This returns 0.</p>
<p>By contrast, the code</p>
<pre>FullSimplify[
 Cosh[ArcCosh[x] + ArcCosh[y]] - x y - Sqrt[x^2 - 1] Sqrt[y^2 - 1]]</pre>
<p>simply returns its input with no simplification, unless we add restrictions on <em>x</em> and <em>y</em>. The code</p>
<pre>FullSimplify[
 Cosh[ArcCosh[x] + ArcCosh[y]] - x y - Sqrt[x^2 - 1] Sqrt[y^2 - 1], 
 Assumptions -&gt; {x &gt; -1 &amp;&amp; y &gt; -1}]
</pre>
<p>does return 0.</p>
<h2>Related posts</h2>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/2026/03/12/arccos/">Inverse cosine</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2016/12/17/branch-cuts-and-common-lisp/">Branch cuts and Common Lisp</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2025/01/22/duplicating-hankel-plot-from-as/">Duplicating a plot from A &amp; S</a></li>
</ul>The post <a href="https://www.johndcook.com/blog/2026/05/19/closer-look-at-an-identity/">Closer look at an identity</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Approximating Markov&#8217;s equation</title>
		<link>https://www.johndcook.com/blog/2026/05/19/zagiers-equation/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 19 May 2026 12:09:54 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247025</guid>

					<description><![CDATA[<p>Markov numbers are integer solutions to x² + y² + z² = 3xyz. The Wikipedia article on Markov numbers mentions that Don Zagier studied Markov numbers by looking the approximating equation x² + y² + z² = 3xyz + 4/9 which is equivalent to f(x) + f(y) = f(z) where f(t) is defined as arccosh(3t/2). It wasn&#8217;t clear to me why the [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/19/zagiers-equation/">Approximating Markov’s equation</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>Markov numbers are integer solutions to</p>
<p style="padding-left: 40px;"><em>x</em>² + <em>y</em>² + <em>z</em>² = 3<em>xyz</em>.</p>
<p>The Wikipedia article on Markov numbers mentions that Don Zagier studied Markov numbers by looking the approximating equation</p>
<p style="padding-left: 40px;"><em>x</em>² + <em>y</em>² + <em>z</em>² = 3<em>xyz</em> + 4/9</p>
<p>which is equivalent to</p>
<p style="padding-left: 40px;"><em>f</em>(<em>x</em>) + <em>f</em>(<em>y</em>) = <em>f</em>(<em>z</em>)</p>
<p>where <em>f</em>(<em>t</em>) is defined as arccosh(3<em>t</em>/2). It wasn&#8217;t clear to me why the two previous equations are equivalent, so I&#8217;m writing this post to show that they are equivalent.</p>
<h2>Examples</h2>
<p>Before showing the equivalence of Zagier&#8217;s two equations, let&#8217;s look at an example that shows solutions to his second equation approximate solutions to Markov&#8217;s equation.</p>
<p>The following code verifies that (5, 13, 194) is a solution to Markov&#8217;s equation.</p>
<pre>x, y, z = 5, 13, 194
assert(x**2 + y**2 + z**2 == 3*x*y*z)
</pre>
<p>With the same <em>x</em> and <em>y</em> above, let&#8217;s show that the <em>z</em> in Zagier&#8217;s second equation is close to the <em>z</em> above.</p>
<pre>from math import cosh, acosh

f = lambda t: acosh(3*t/2)
g = lambda t: cosh(t)*2/3
z = g(f(x) + f(y))
print(z)
</pre>
<p>This gives <em>z</em> = 194.0023, which is close to the value of <em>z</em> in the Markov triple above.</p>
<h2>Applying Osborn&#8217;s rule</h2>
<p>Now suppose</p>
<p style="padding-left: 40px;"><em>f</em>(<em>x</em>) + <em>f</em>(<em>y</em>) = <em>f</em>(<em>z</em>)</p>
<p>which expands to</p>
<p style="padding-left: 40px;">arccosh(3<em>x</em>/2) + arccosh(3<em>y</em>/2)  = arccosh(3<em>z</em>/2).</p>
<p>It seems sensible to apply cosh to both sides. Is there some identity for cosh of a sum? Maybe you recall the equation for cosine of a sum:</p>
<p style="padding-left: 40px;">cos(<em>a</em> + <em>b</em>) = cos(<em>a</em>) cos(<em>b</em>) − sin(<em>a</em>) sin(<em>b</em>).</p>
<p>Then <a href="https://www.johndcook.com/blog/2024/08/20/osborn-rule/">Osborn&#8217;s rule</a> says the corresponding hyperbolic identity is</p>
<p style="padding-left: 40px;">cosh(<em>a</em> + <em>b</em>) = cosh(<em>a</em>) cosh(<em>b</em>) − sinh(<em>a</em>) sinh(<em>b</em>).</p>
<p>Osborn&#8217;s rule also says that the analog of the familiar identity</p>
<p style="padding-left: 40px;">sin²(<em>a</em>) + cos²(<em>b</em>) = 1</p>
<p>is</p>
<p style="padding-left: 40px;">sinh²(<em>a</em>) = cosh²(<em>b</em>) − 1.</p>
<p>From these two hyperbolic identities we can show that [1]</p>
<p style="padding-left: 40px;">cosh( arccosh(<em>a</em>) + arccosh(<em>b</em>) ) = <em>ab</em> + √(<em>a</em>² − 1) √(<em>b</em>² − 1).</p>
<h2>Slug it out</h2>
<p>The identity derived above is the tool we need to reduce our task to routine algebra.</p>
<p>If</p>
<p style="padding-left: 40px;">arccosh(3<em>x</em>/2) + arccosh(3<em>y</em>/2)  = arccosh(3<em>z</em>/2)</p>
<p>then</p>
<p style="padding-left: 40px;">(3<em>x</em>/2)  (3<em>y</em>/2)  + √((3<em>x</em>/2)² − 1) √((3<em>y</em>/2)² − 1) = 3<em>z</em> / 2</p>
<p>which simplifies to Zagier&#8217;s equation</p>
<p style="padding-left: 40px;"><em>x</em>² + <em>y</em>² + <em>z</em>² = 3<em>xyz</em> + 4/9.</p>
<h2>Related posts</h2>
<ul>
<li class="link"><a href="https://www.johndcook.com/blog/2023/09/22/prime-weeds/">Primes and weeds</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2026/03/10/sinh-arccosh/">sinh( arccosh(x) )</a></li>
<li class="link"><a href="https://www.johndcook.com/blog/2022/01/08/diophantine/">Three paths converge</a></li>
</ul>
<p>[1] The equation holds at least for <em>x</em> &gt; 1 and <em>y</em> &gt; 1, which is enough for this post. More general arguments run into complications due to branch cuts.</p>The post <a href="https://www.johndcook.com/blog/2026/05/19/zagiers-equation/">Approximating Markov’s equation</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Recovering the state of xorshift128</title>
		<link>https://www.johndcook.com/blog/2026/05/15/xorshift128-state/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Fri, 15 May 2026 12:34:29 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[RNG]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247024</guid>

					<description><![CDATA[<p>I&#8217;ve written a couple posts lately about reverse engineering the internal state of a random number generator, first Mersenne Twister then lehmer64. This post will look at xorshift128, implemented below. import random # Seed the generator state a: int = random.getrandbits(32) b: int = random.getrandbits(32) c: int = random.getrandbits(32) d: int = random.getrandbits(32) MASK = [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/15/xorshift128-state/">Recovering the state of xorshift128</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve written a couple posts lately about reverse engineering the internal state of a random number generator, first <a href="https://www.johndcook.com/blog/2026/05/10/reverse-mersenne-twister/">Mersenne Twister</a> then <a href="https://www.johndcook.com/blog/2026/05/12/hacking-the-lehmer64-rng/">lehmer64</a>. This post will look at xorshift128, implemented below.</p>
<pre>import random

# Seed the generator state
a: int = random.getrandbits(32)
b: int = random.getrandbits(32)
c: int = random.getrandbits(32)
d: int = random.getrandbits(32)

MASK = 0xFFFFFFFF

def xorshift128() -&gt; int:
    global a, b, c, d

    t = d
    s = a

    t ^= (t &lt;&lt; 11) &amp; MASK t ^= (t &gt;&gt;  8) &amp; MASK
    s ^= (s &gt;&gt; 19) &amp; MASK

    a, b, c, d = (t ^ s) &amp; MASK, a, b, c

    return a
</pre>
<h2>Recovering state</h2>
<p>Recovering the internal state of the generator is simple: it&#8217;s the four latest outputs in reverse order. This is illustrated by the following chart.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/xorshift128_chart.png" alt=" a b c d output 5081e5ca 79259a41 63e12955 651e537d c793d11c c793d11c 5081e5ca 79259a41 63e12955 ad52e33a ad52e33a c793d11c 5081e5ca 79259a41 f8f09343 f8f09343 ad52e33a c793d11c 5081e5ca a7009622 a7009622 f8f09343 ad52e33a c793d11c fe42a8ef " width="400" height="111" /></p>
<p>This means that once we&#8217;ve seen four outputs, we can predict the rest of the outputs. The following code demonstrates this.</p>
<p>Let&#8217;s generate five random values.</p>
<pre>out = [xorshift128() for _ in range(5)]</pre>
<p>Running</p>
<pre>print(hex(out[4]))
</pre>
<p>shows the output 0xc3f4795d.</p>
<p>If we reset the state of the generator using the first four outputs</p>
<pre>d, c, b, a, _ = out
print(hex(xorshift128()))
</pre>
<p>we get the same result.</p>
<h2>Good stats, bad security</h2>
<p>Mersenne Twister and lehmer64 have good statistical properties, despite being predictable. The xorshift128 generator is even easier to predict, but it also has good statistical properties. These generators would be fine for many applications, such as Monte Carlo simulation, but disastrous for use in cryptography.</p>
<p>The post on lehmer64 mentioned at the end that the internal state of PCG64 can also be recovered from its output. However, doing so requires far more sophisticated math and thousands of hours of compute time. Still, it&#8217;s not adequate for cryptography. For that you&#8217;d need a random number generator designed to be secure, such as <a href="https://www.johndcook.com/blog/2019/03/02/chacha-adiantum/">ChaCha</a>.</p>
<p>So why not just use a cryptographically secure random number generator (CSPRNG) for everything? You could, but the other generators mentioned in this post use less memory and are much faster. PCG64 occupies an interesting middle ground: simple and fast, but not easily reversible.</p>The post <a href="https://www.johndcook.com/blog/2026/05/15/xorshift128-state/">Recovering the state of xorshift128</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Initialize and print 128-bit integers in C</title>
		<link>https://www.johndcook.com/blog/2026/05/12/c-128-bit-int/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 12 May 2026 12:20:20 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[Programming]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247022</guid>

					<description><![CDATA[<p>If you look very closely at my previous post, you&#8217;ll notice that I initialize a 128-bit integer with a 64-bit value. The 128-bit unsigned integer represents the internal state of a random number generator. Why not initialize it to a 128-bit value? I was trying to keep the code simple. A surprising feature of C [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/12/c-128-bit-int/">Initialize and print 128-bit integers in C</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>If you look very closely at my previous post, you&#8217;ll notice that I initialize a 128-bit integer with a 64-bit value. The 128-bit unsigned integer represents the internal state of a random number generator. Why not initialize it to a 128-bit value? I was trying to keep the code simple.</p>
<p>A surprising feature of C compilers, at least of GCC and Clang, is that you cannot initialize a 128-bit integer to a 128-bit integer <strong>literal</strong>. You can&#8217;t directly print a 128-bit integer either, which is why the previous post introduces a function <code>print_u128</code>.</p>
<p>The code</p>
<pre>__uint128_t x = 0x00112233445566778899aabbccddeeff;</pre>
<p>Produces the following error message.</p>
<pre>error: integer literal is too large to be represented in any integer type</pre>
<p>The problem isn&#8217;t initializing a 128-bit number to a 128-bit value; the problem is that the compiler cannot parse the literal expression</p>
<pre>0x00112233445566778899aabbccddeeff</pre>
<p>One solution to the problem is to introduce the macro</p>
<pre>#define U128(hi, lo) (((__uint128_t)(hi) &lt;&lt; 64) | (lo))</pre>
<p>and use it to initialize the variable.</p>
<pre>__uint128_t x = U128(0x0011223344556677, 0x8899aabbccddeeff);</pre>
<p>You can verify that <code>x</code> has the intended state by calling <code>print_u128</code> from the previous post.</p>
<pre>void print_u128(__uint128_t n)
{
    printf("0x%016lx%016lx\n",
           (uint64_t)(n &gt;&gt; 64),      // upper 64 bits
           (uint64_t)n);             // lower 64 bits
}
</pre>
<p>Then</p>
<pre>print_u128(x);</pre>
<p>prints</p>
<pre>0x00112233445566778899aabbccddeeff</pre>
<p><b>Update</b>. The code for <code>print_u128</code> above compiles cleanly with <code>gcc</code> but <code>clang</code> gives the following warning.</p>
<pre>warning: format specifies type 'unsigned long' but the argument has type 'uint64_t' (aka 'unsigned long long') [-Wformat]</pre>
<p>You can suppress the warning by including the <code>inttypes</code> header and modifying the <code>print_u128</code> function.</p>
<p>Here&#8217;s the final code. It compiles cleanly under <code>gcc</code> and <code>clang</code>.</p>
<pre>#include &lt;stdio.h&gt;
#include &lt;stdint.h&gt;
#include &lt;inttypes.h&gt;
#define U128(hi, lo) (((__uint128_t)(hi) &lt;&lt; 64) | (lo))

void print_u128(__uint128_t n)
{
    printf("0x%016" PRIx64 "%016" PRIx64 "\n",
           (uint64_t)(n &gt;&gt; 64),
           (uint64_t)n);
}

int main(void)
{
    __uint128_t x = U128(0x0011223344556677, 0x8899aabbccddeeff);
    print_u128(x);
    return 0;
}
</pre>The post <a href="https://www.johndcook.com/blog/2026/05/12/c-128-bit-int/">Initialize and print 128-bit integers in C</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hacking the lehmer64 RNG</title>
		<link>https://www.johndcook.com/blog/2026/05/12/hacking-the-lehmer64-rng/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 12 May 2026 11:07:31 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[Cryptography]]></category>
		<category><![CDATA[RNG]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247020</guid>

					<description><![CDATA[<p>A couple days ago I wrote about hacking the Mersenne Twister. I explained how to recover the random number generator&#8217;s internal state from a stream of 640 outputs. This post will do something similar with the lehmer64 random number generator. This generator is very simple to implement. Daniel Lemire found it to be &#8220;the fastest [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/12/hacking-the-lehmer64-rng/">Hacking the lehmer64 RNG</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>A couple days ago I wrote about hacking the Mersenne Twister. I explained how to recover the random number generator&#8217;s internal state from a stream of 640 outputs.</p>
<p>This post will do something similar with the lehmer64 random number generator. This generator is very simple to implement. <a href="https://lemire.me/blog/2019/03/19/the-fastest-conventional-random-number-generator-that-can-pass-big-crush/">Daniel Lemire</a> found it to be &#8220;the fastest conventional random number generator that can pass Big Crush,&#8221; a well-respected test for pseudorandom number generators.</p>
<h2>Implementing lehmer64</h2>
<p>The lehmer64 generator can be implemented in C by</p>
<pre>__uint128_t g_lehmer64_state;

uint64_t lehmer64() {
    g_lehmer64_state *= 0xda942042e4dd58b5ULL;  
  return g_lehmer64_state &gt;&gt; 64;
}
</pre>
<p>The analogous code in Python would have to simulate the overflow behavior of a 128-bit integer by reducing the state mod 2<sup>128</sup> after the multiplication.</p>
<p>Reverse engineering lehmer64 is easier than reverse engineering the Mersenne Twister because only three outputs are needed. However, the theory behind the exploit is more sophisticated. See [1].</p>
<p>The following code sets the state to an arbitrary initial seed value and generates three values.</p>
<pre>#include &lt;stdio.h&gt;
#include &lt;stdint.h&gt;

int main(void)
{
    g_lehmer64_state = 0x4789499d78770934; // seed
    for (int i = 0; i &lt; 3; i++) {
        printf("0x%016lx\n", lehmer64());
    }

    return 0;
}
</pre>
<p>The code prints the following.</p>
<pre>0x3d144d12822bcc2e
0x85a67226191a568d
0x53e803dffc88e8f8
</pre>
<h2>Exploiting lehmer64</h2>
<p>Here is Python code for recovering the state of the lehmer64 generator given in [1].</p>
<pre>def reconstruct(X):
    a = 0xda942042e4dd58b5
    r = round(2.64929081169728e-7 * X[0] + 3.51729342107376e-7 * X[1] + 3.89110109147656e-8 * X[2])
    s = round(3.12752538137199e-7 * X[0] - 1.00664345453760e-7 * X[1] - 2.16685184476959e-7 * X[2])
    t = round(3.54263598631140e-8 * X[0] - 2.05535734808162e-7 * X[1] + 2.73269247090513e-7 * X[2])
    u = r * 1556524 + s * 2249380 + t * 1561981
    v = r * 8429177212358078682 + s * 4111469003616164778 + t * 3562247178301810180
    state = (a*u + v) % (2**128)
    return state
</pre>
<p>Let&#8217;s call <code>reconstruct</code> with the output of our C code.</p>
<pre>
X = [0x3d144d12822bcc2e, 0x85a67226191a568d, 0x53e803dffc88e8f8]
print( hex( reconstruct(X) ) )
</pre>
<p>This prints </p>
<pre>0x3d144d12822bcc2e1b81101c593761c4</pre>
<p>Now for the confusing part: at what point is the number above the state of the generator? Is it the state before or after generating the three values? Neither! It is the state after generating the first value.</p>
<p>We can verify this by modifying the C code as follows and rerunning it.</p>
<pre>
void print_u128(__uint128_t n)
{
    printf("0x%016lx%016lx\n",
           (uint64_t)(n >> 64),      // upper 64 bits
           (uint64_t)n);             // lower 64 bits
}

int main(void)
{
    g_lehmer64_state = 0x4789499d78770934; // seed
    for (int i = 0; i < 3; i++) {
        printf("0x%016lx\n", lehmer64());
        printf("state: ");
        print_u128(g_lehmer64_state);
    }
 
    return 0;
}
</pre>
<p>The main goal of [1] is to recover the state of the PCG generator, not the lehmer64 generator. The latter was a side quest. Recovering the state of PCG64 is much harder; the authors estimate it takes about 20,000 CPU-hours. The paper shows that a technique used as part of pursuing their main goal can quickly recover the lehmer64 state.</p>
<h2>Related posts</h2>
<ul>
<li class='link'><a href='https://www.johndcook.com/blog/2026/05/10/reverse-mersenne-twister/'>Recovering the Mersenne Twister internal state with linear algebra</a></li>
<li class='link'><a href='https://www.johndcook.com/blog/2017/07/07/testing-the-pcg-random-number-generator/'>Testing the PCG random number generator</a></li>
<li class='link'><a href='https://www.johndcook.com/blog/rng-testing/'>Random number generator testing</a></li>
</ul>
<p>[1] Charles Bouillaguet, Florette Martinez, and Julia Sauvage. Practical seed-recovery for the PCG Pseudo-Random Number Generator. IACR Transactions on Symmetric Cryptology. ISSN 2519-173X, Vol. 2020, No. 3, pp. 175–196.</p>The post <a href="https://www.johndcook.com/blog/2026/05/12/hacking-the-lehmer64-rng/">Hacking the lehmer64 RNG</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Euler function</title>
		<link>https://www.johndcook.com/blog/2026/05/11/euler-function/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Tue, 12 May 2026 00:49:41 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247019</guid>

					<description><![CDATA[<p>This morning I wrote a post about the probability that a random matrix over a finite field is invertible. If the field has q elements and the matrix has dimensions n × n then the probability is In that post I made observation that p(q, n) converges very quickly as a function of n [1]. [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/11/euler-function/">Euler function</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>This morning I wrote a <a href="https://www.johndcook.com/blog/2026/05/11/random-binary-matrices/">post</a> about the probability that a random matrix over a finite field is invertible. If the field has <em>q</em> elements and the matrix has dimensions <em>n</em> × <em>n</em> then the probability is</p>
<p><img class='aligncenter' src='https://www.johndcook.com/euler_fun1.svg' alt='p(q, n) = \prod_{i=1}^n \left(1 - \frac{1}{q^i}\right)' style='background-color:white' height='54' width='175' /></p>
<p>In that post I made observation that <em>p</em>(<em>q</em>, <em>n</em>) converges very quickly as a function of <em>n</em> [1]. One way to see that the convergence is quick is to note that</p>
<p><img class='aligncenter' src='https://www.johndcook.com/euler_fun2.svg' alt='\prod_{i=1}^\infty \left(1 - \frac{1}{q^i}\right) = \prod_{i=1}^n \left(1 - \frac{1}{q^i}\right) \, \prod_{i=n+1}^\infty \left(1 - \frac{1}{q^i}\right)' style='background-color:white' height='54' width='336' /></p>
<p>and</p>
<p><img class='aligncenter' src='https://www.johndcook.com/euler_fun3.svg' alt='\prod_{i=n+1}^\infty \left(1 - \frac{1}{q^i}\right) = 1 - {\cal O}\left(\frac{1}{q^{n+1}}\right)' style='background-color:white' height='54' width='237' /></p>
<p>John Baez pointed out in the comments that <em>p</em>(<em>q</em>, ∞) = φ(1/<em>q</em>) where φ is the Euler function.</p>
<p>Euler was extremely prolific, and many things are named after him. Several functions are known as Euler&#8217;s function, the most common being his totient function in number theory. The Euler function we&#8217;re interested in here is</p>
<p><img class='aligncenter' src='https://www.johndcook.com/euler_fun4.svg' alt='\phi(x) = \prod_{i=1}^\infty \left( 1 - x^i \right)' style='background-color:white' height='54' width='154' /></p>
<p>for −1 &lt; x &lt; 1. Usually the argument of φ is denoted &#8220;<em>q</em>&#8221; but that would be confusing in our context because our <em>q</em>, the number of elements in a field, is the reciprocal of Euler&#8217;s <em>q</em>, i.e. <em>x</em> = 1/<em>q</em>.</p>
<p>Euler&#8217;s identity [2] (in this context, not to be confused with other Euler identities!) says</p>
<p><img class='aligncenter' src='https://www.johndcook.com/euler_fun5.svg' alt='\phi(x) = \sum_{n=-\infty}^\infty (-1)^n x^{(3n^2 - n)/2}' style='background-color:white' height='53' width='222' /></p>
<p>This function is easy to calculate because the series converges very quickly. From the alternating series theorem we have</p>
<p><img class='aligncenter' src='https://www.johndcook.com/euler_fun6.svg' alt='\phi(x) = \sum_{n=-N}^N (-1)^n (-1)^n x^{(3n^2 - n)/2} + {\cal O}\left( x^{(3(N+1)^2 - (N+1))/2} \right)' style='background-color:white' height='57' width='464' /></p>
<p>When <em>q</em> = 2 and so <em>x</em> = 1/2, <em>N</em> = 6 is enough to compute φ(<em>x</em>) with an error less than 2<sup>−70</sup>, beyond the precision of a floating point number. When <em>q</em> is larger, even fewer terms are needed.</p>
<p>To illustrate this, we have the following Python script.</p>
<pre>
def phi(x, N):
    s = 0
    for n in range(-N, N+1):
        s += (-1)**n * x**((3*n**2 - n)/2)
    return s

print(phi(0.5, 6))
</pre>
<p>Every digit in the output is correct.</p>
<h2>Related posts</h2>
<ul>
<li class='link'><a href='https://www.johndcook.com/blog/2024/07/23/q-pochhammer/'>q-analogs of rising powers</a></li>
<li class='link'><a href='https://www.johndcook.com/blog/2021/11/11/another-pentagonal-number-theorem/'>Another pentagonal number theorem</a></li>
</ul>
<p>[1] I didn&#8217;t say that explicitly, but I pointed out that <em>p</em>(2, 8) was close to <em>p</em>(2, ∞).</p>
<p>[2] This identity is also known as the pentagonal number theorem because of its connection to <a href="https://www.johndcook.com/blog/2021/11/10/partitions-and-pentagons/">pentagonal numbers</a>.</p>The post <a href="https://www.johndcook.com/blog/2026/05/11/euler-function/">Euler function</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Inverse shift</title>
		<link>https://www.johndcook.com/blog/2026/05/11/inverse-shift/</link>
					<comments>https://www.johndcook.com/blog/2026/05/11/inverse-shift/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Mon, 11 May 2026 15:30:30 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[Linear algebra]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247017</guid>

					<description><![CDATA[<p>What is the inverse of shifting a sequence to the right? Shifting it to the left, obviously. But wait a minute. Suppose you have a sequence of eight bits abcdefgh and you shift it to the right. You get 0abcdefg. If you shift this sequence to the left you get abcdefg0 You can&#8217;t recover the [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/11/inverse-shift/">Inverse shift</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>What is the inverse of shifting a sequence to the right? Shifting it to the left, obviously.</p>
<p>But wait a minute. Suppose you have a sequence of eight bits</p>
<p style="padding-left: 40px;"><em>abcdefgh</em></p>
<p>and you shift it to the right. You get</p>
<p style="padding-left: 40px;">0<em>abcdefg.</em></p>
<p>If you shift this sequence to the left you get</p>
<p style="padding-left: 40px;"><em>abcdefg</em>0</p>
<p>You can&#8217;t recover the last element <em>h</em> because the right-shift destroyed information about <em>h</em>.</p>
<p>A left-shift doesn&#8217;t fully recover a right-shift, and yet surely left shift and right shift are in some sense inverses.</p>
<p><a href="https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/">Yesterday</a> I wrote a post about representing bit manipulations, including shifts, as matrix operators. The matrix corresponding to shifting right by <em>k</em> bits has 1s on the <em>k</em>th diagonal above the main diagonal and 0s everywhere else. For example, here is the matrix for shifting an 8-bit number right two bits. A black square represents a 1 and a white square represents a 0.</p>
<p><img loading="lazy" decoding="async" class="aligncenter" src="https://www.johndcook.com/right_shift_2_matrix.png" width="360" height="360" /></p>
<p>This matrix isn&#8217;t invertible. When you&#8217;d like to take the inverse of a non-invertible matrix, your knee jerk response should be to compute the pseudoinverse. (Technically the <a href="https://www.johndcook.com/blog/2018/05/05/svd/">Moore-Penrose pseudoinverse</a>. There are <a href="https://www.johndcook.com/blog/2025/05/21/drazin-pseudoinverse/">other</a> pseudoinverses, but Moore-Penrose is the most common.)</p>
<p>As you might hope/expect, the pseudoinverse of a right-shift matrix is a left-shift matrix. In this case the pseudoinverse is simply the transpose, though of course that isn&#8217;t always the case.</p>
<p><img loading="lazy" decoding="async" class="aligncenter" src="https://www.johndcook.com/left_shift_2_matrix.png" width="360" height="360" /></p>
<p>If you&#8217;d like to prove that the pseudoinverse of a matrix that shifts right by <em>k</em> places is a matrix that shifts left by <em>k</em> places, you don&#8217;t have to compute the pseudo inverse per se: you can verify your guess. <a href="https://www.johndcook.com/blog/2018/04/24/moore-penrose-pseudoinverse-is-not-an-adjoint/">This post</a> gives four requirements for a pseudoinverse. You can prove that left shift is the inverse of right shift by showing that it satisfies the four equations.</p>The post <a href="https://www.johndcook.com/blog/2026/05/11/inverse-shift/">Inverse shift</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/11/inverse-shift/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Probability that a random binary matrix is invertible</title>
		<link>https://www.johndcook.com/blog/2026/05/11/random-binary-matrices/</link>
					<comments>https://www.johndcook.com/blog/2026/05/11/random-binary-matrices/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Mon, 11 May 2026 13:25:13 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Linear algebra]]></category>
		<category><![CDATA[Probability]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247016</guid>

					<description><![CDATA[<p>The two latest posts have involved invertible matrices with 0 and 1 entries. If you fill an n × n matrix with 0s and 1s randomly, how likely is it to be invertible? What kind of inverse? There are a couple ways to find the probability that a binary matrix is invertible, depending on what [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/11/random-binary-matrices/">Probability that a random binary matrix is invertible</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The two latest posts have involved invertible matrices with 0 and 1 entries. If you fill an <em>n</em> × <em>n</em> matrix with 0s and 1s randomly, how likely is it to be invertible?</p>
<h2>What kind of inverse?</h2>
<p>There are a couple ways to find the probability that a binary matrix is invertible, depending on what you mean by the inverse.</p>
<p>Suppose you have a matrix <em>M</em> filled with 0s and 1s and you&#8217;re looking for a matrix <em>N</em> such that <em>MN</em> is the identity matrix. Do you want the entries of <em>N</em> to also be 0s and 1s? And when you multiply the matrices, are you doing ordinary integer arithmetic or are you working mod 2?</p>
<p>In the previous posts we were working over GF(2), the field with two elements, 0 and 1. All the elements of a matrix are either 0 or 1, and arithmetic is carried out mod 2. In that context there&#8217;s a nice expression for the probability a square matrix is invertible.</p>
<p>If you&#8217;re working over the real numbers, the probability of binary matrix being invertible is higher. One way to see this is that the inverse of a binary matrix is allowed to be binary but it isn&#8217;t required to be.</p>
<p>Another way to see this is to look at determinants. If you think of a matrix <em>M</em> as a real matrix whose entries happen to only be 0 or 1, <em>M</em> is invertible if and only its determinant is non-zero. But if you think of <em>M</em> as a matrix over GF(2), the entries are either 0 or 1 out of necessity, and <em>M</em> is invertible if and only if its determinant, <em>computed in</em> GF(2), is non-zero. If the determinant of <em>M</em> as a real matrix is a non-zero even number, then <em>M</em> is invertible as a real matrix but not as a matrix over GF(2).</p>
<h2>Probability of invertibility in GF(2)</h2>
<p>Working over GF(2), what is the probability that a random matrix is invertible? Turns out it&#8217;s just as easy to answer a more general question: what is the probability that a random <em>n</em> × <em>n</em> matrix over GF(<em>q</em>), a finite field with <em>q</em> elements, is invertible? This is</p>
<p><img loading="lazy" decoding="async" class="aligncenter" style="background-color: white;" src="https://www.johndcook.com/prob_finite_field_matrix_invertible.svg" alt="\prod_{i=1}^n\left( 1 - \frac{1}{q^i} \right)" width="98" height="54" /></p>
<p>When <em>q</em> = 2 and <em>n</em> = 8 this probability is 0.289919. The probability is roughly the same for all larger values of <em>n</em>, converging to approximately 0.288788 as <em>n</em> → ∞.</p>
<h2>Probability of invertibility in ℝ</h2>
<p>What is the probability that an 8 × 8 matrix with random 0 and 1 entries is invertible as a real matrix? We can estimate this by simulation.</p>
<pre>import numpy as np

def simulate_prob_invertible_real(n, numreps=1000):
    s = 0
    for _ in range(numreps):
        M = np.random.randint(0, 2, size=(n, n))
        det = np.linalg.det(M)
        if abs(det) &gt; 1e-9:
            s += 1
    return s/numreps
</pre>
<p>When <em>n</em> = 8, I got 0.5477 when running the code with 10,000 reps.</p>
<p>When <em>n</em> = 32, I got a probability of 1. Obviously it is possible for a 32 × 32 binary matrix to be singular, but it&#8217;s very unlikely: it didn&#8217;t happen in 10,000 random draws.</p>The post <a href="https://www.johndcook.com/blog/2026/05/11/random-binary-matrices/">Probability that a random binary matrix is invertible</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/11/random-binary-matrices/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>The linear algebra of bit twiddling</title>
		<link>https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/</link>
					<comments>https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/#comments</comments>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sun, 10 May 2026 18:51:40 +0000</pubDate>
				<category><![CDATA[Computing]]></category>
		<category><![CDATA[Math]]></category>
		<category><![CDATA[Linear algebra]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247014</guid>

					<description><![CDATA[<p>The previous post looked at the tempering step of the Mersenne Twister, formulating a sequence of bit operations as multiplication by a matrix mod 2. This post will look at the components more closely. The theorems of linear algebra generally hold independent of the field of scalars. Typically the field is ℝ or ℂ, but [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/">The linear algebra of bit twiddling</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The <a href="https://www.johndcook.com/blog/2026/05/10/reverse-mersenne-twister/">previous post</a> looked at the tempering step of the Mersenne Twister, formulating a sequence of bit operations as multiplication by a matrix mod 2. This post will look at the components more closely.</p>
<p>The theorems of linear algebra generally hold independent of the field of scalars. Typically the field is ℝ or ℂ, but most of basic linear algebra works the same over every field [1]. In particular, we can do linear algebra over a finite field, and we&#8217;re interested in the most finite of finite fields GF(2), the field with just two elements, 0 and 1.</p>
<p>In GF(2), addition corresponds to XOR. We will denote this by ⊕ to remind us that although it&#8217;s addition, it&#8217;s not the usual addition, i.e. 1 ⊕ 1 = 0. Similarly, multiplication corresponds to AND. We&#8217;ll work with 8-bit numbers to make the visuals easier to see.</p>
<p>Shifting a number left one bit corresponds to multiplication by a matrix with 1&#8217;s below the diagonal main. Shifting left by <em>k</em> bits is the same as shifting left by 1 bit <em>k</em> times, so the the matrix representation for <em>x</em> &lt;&lt; <em>k</em> is the <em>k</em>th power of the matrix representation of shifting left once. This matrix has 1s on the <em>k</em>th diagonal below the main diagonal. Below is the matrix for shifting left two bits, <em>x</em> &lt;&lt; <em>k</em>.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/left_shift_2_matrix.png" width="360" height="360" /></p>
<p>Right shifts are the mirror image of left shifts. Here&#8217;s the matrix for shifting right two bits, <em>x</em> &gt;&gt; <em>k</em>.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/right_shift_2_matrix.png" width="360" height="360" /></p>
<p>Shifts are not fully invertible because bits either fall off the left or the right end. The steps in the Mersenne Twister are invertible because shifts are always XOR&#8217;d with the original argument. For example, although the function that takes <em>x</em> to <em>x</em> &gt;&gt; 2 is not invertible, the function that takes <em>x</em> to <em>x</em> ⊕ (<em>x</em> &gt;&gt; 2) is invertible. This operation corresponds to the matrix below.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/right_shift_xor.png" width="360" height="360" /></p>
<p>This is an upper triangular matrix, so its determinant is the product of the diagonal elements. These are all 1s, so the determinant is 1, and the matrix is invertible.</p>
<p>Bitwise AND multiplies each bit of the input by the corresponding bit in another number known as the mask. The bits aligned with a 1 are kept and the bits aligned with a 0 are cleared. This corresponds to multiplying by a diagonal matrix whose diagonal elements correspond to the bits in the mask. For example, here is the matrix that corresponds to taking the bitwise AND with 10100100.</p>
<p><img loading="lazy" decoding="async" src="https://www.johndcook.com/mask_matrix.png" width="360" height="360" class="aligncenter size-medium" /></p>
<p>Each of the steps in the Mersenne Twister tempering process are invertible because they all correspond to triangular matrices with all 1&#8217;s on the diagonal. For example, the line</p>
<pre>y ^= (y <<  7) &#038; 0x9d2c5680 </pre>
<p>says to shift the bits of <code>y</code> left 7 places, then zero out the elements corresponding to 0s in the mask, then XOR the result with <em>y</em>. In matrix terms, we multiply by a lower triangular matrix with zeros on the main diagonal, then multiply by a diagonal matrix that zeros out some of the terms, then add the identity matrix. So the matrix corresponding to the line of code above is lower triangular, with all 1s on the diagonal, so it is invertible. </p>
<p>[1] Until you get to eigenvalues. Then it matters whether the field is algebraically complete, which no finite field is.</p>The post <a href="https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/">The linear algebra of bit twiddling</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
					<wfw:commentRss>https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Reverse engineering Mersenne Twister with Linear Algebra</title>
		<link>https://www.johndcook.com/blog/2026/05/10/reverse-mersenne-twister/</link>
		
		<dc:creator><![CDATA[John]]></dc:creator>
		<pubDate>Sun, 10 May 2026 17:32:02 +0000</pubDate>
				<category><![CDATA[Math]]></category>
		<category><![CDATA[Linear algebra]]></category>
		<category><![CDATA[RNG]]></category>
		<guid isPermaLink="false">https://www.johndcook.com/blog/?p=247013</guid>

					<description><![CDATA[<p>The Mersenne Twister (MT) is a random number generator with good statistical properties but bad cryptographic properties. In buzzwords, it&#8217;s a PRNG but not a CSPRNG. This post will show how the internal state of a MT generator can be recovered from its output. We&#8217;ll do this using linear algebra. The bit twiddling approach is [&#8230;]</p>
The post <a href="https://www.johndcook.com/blog/2026/05/10/reverse-mersenne-twister/">Reverse engineering Mersenne Twister with Linear Algebra</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></description>
										<content:encoded><![CDATA[<p>The Mersenne Twister (MT) is a random number generator with good statistical properties but bad cryptographic properties. In buzzwords, it&#8217;s a PRNG but not a CSPRNG.</p>
<p>This post will show how the internal state of a MT generator can be recovered from its output. We&#8217;ll do this using linear algebra. The bit twiddling approach is more common and more efficient, but the linear algebra approach is conceptually simpler.</p>
<h2>How MT works</h2>
<p>There are numerous variations on the Mersenne Twister. We&#8217;ll focus on the original version that had internal state consists of vector <code>x</code> of length 640 filled with 32-bit numbers. The ideas in this post would apply equally to all MT versions.</p>
<p>The first call to MT returns a &#8220;tempered&#8221; version of <code>x[0]</code>. The next call returned a tempered version of <code>x[1]</code>, and so on. After every 640 calls, the state is scrambled. This scrambling is where the &#8220;twist&#8221; in the name Mersenne Twister comes from. (The Mersenne part comes from the fact that the period of an MT generator is a <a href="https://www.johndcook.com/blog/2011/09/09/five-interesting-things-about-mersenne-primes/">Mersenne prime</a>.)</p>
<h2>Tempering</h2>
<p>Here is Python code for performing the tempering step.</p>
<pre>def temper(y):
    y ^= (y &gt;&gt; 11) 
    y ^= (y &lt;&lt;  7) &amp; 0x9d2c5680 
    y ^= (y &lt;&lt; 15) &amp; 0xefc60000 
    y ^= (y &gt;&gt; 18)  
    return y
</pre>
<p>Each step is reversible, and so the <code>temper</code> function is reversible.</p>
<p>Because the tempering step is reversible, the first output can be used to infer the first element of the internal state, the second output to infer the second element, and so on. After 640 calls one can know the entire internal state and predict the rest of the generator&#8217;s output from then on.</p>
<h2>Linear algebra</h2>
<p>The bitwise operations above all correspond to linear operations over GF(2), the field with just two elements, 0 and 1. Addition in this field is XOR and multiplication is AND.</p>
<p>Each step corresponds to multiplying a vector of 32 bits on the left by a 32 × 32 matrix with entries that are 0&#8217;s and 1&#8217;s, with the understanding that the sum of two bits is their XOR and the product of two bits is their AND. Equivalently, arithmetic is carried out mod 2. So you can compute the matrix-vector product as ordinary integers if you then reduce every integer mod 2.</p>
<p>We will find the matrix <em>M</em> that corresponds to the temper operation and prove that it is invertible by finding its inverse. This proves that tempering is invertible, and one could compute the inverse of tempering by multiplying by <em>M</em><sup>−1</sup>, though it would be more efficient to undo tempering by bit twiddling.</p>
<p>One way to recover a matrix is to multiplying by unit vectors <em>e</em><sub><em>i</em></sub> where the <em>i</em>th component of <em>e</em><sub><em>i</em></sub> is 1 and the rest of the components are zero. Then</p>
<p style="padding-left: 40px;"><em>M</em> <em>e</em><sub><em>i</em></sub></p>
<p>is the <em>i</em>th column of <em>M</em>.</p>
<p>So we can find the <em>n</em>th column of <em>M</em> by tempering 1 &lt;&lt; <em>n</em> = 2<sup><em>n</em></sup>.</p>
<pre>M = np.zeros((32, 32), dtype=int)
for i in range(32):
    t = temper(1 &lt;&lt; (31-i))
    s = f"{t:032b}"
    for j in range(32):
        M[j, i] = int(s[j])
</pre>
<p>Let&#8217;s generate a random number and compute its tempered form two ways: directly and matrix multiplication.</p>
<pre>x = random.getrandbits(32)
y = temper(x)
print(f"{y:032b}")

vx = np.array([int(b) for b in f"{x:032b}"]) # vector form of x
vy = M @ vx % 2 # vector form of y
print("".join(str(b) for b in vy))
</pre>
<p>Both produce the same bits:</p>
<pre>10100101100101101100110101000110
10100101100101101100110101000110
</pre>
<p>We can find the matrix representation of the untemper function by inverting the matrix <em>M</em>. However, we need to invert it over the field GF(2), not over the integers or reals.</p>
<pre>import galois
GF2 = galois.GF(2)
Minv = np.linalg.inv(GF2(M))
</pre>
<p>Here are visualizations of <em>M</em> and its inverse using a black square for a 1 and a white square for a 0.</p>
<p><em>M</em>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/MT_M.png" width="400" height="400" /></p>
<p><em>M</em><sup>−1</sup>:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium" src="https://www.johndcook.com/MT_U.png" width="400" height="400" /></p>
<p>The <a href="https://www.johndcook.com/blog/2026/05/10/the-linear-algebra-of-bit-twiddling/">next post</a> will back up and look at the linear algebra of the components that comprise the Mersenne Twister.</p>The post <a href="https://www.johndcook.com/blog/2026/05/10/reverse-mersenne-twister/">Reverse engineering Mersenne Twister with Linear Algebra</a> first appeared on <a href="https://www.johndcook.com/blog">John D. Cook</a>.]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
