[{"id":246984,"date":"2026-04-23T10:56:02","date_gmt":"2026-04-23T15:56:02","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246984"},"modified":"2026-04-23T11:08:47","modified_gmt":"2026-04-23T16:08:47","slug":"solve-an-oblique-triangle","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/23\/solve-an-oblique-triangle\/","title":{"rendered":"Approximation to solve an oblique triangle"},"content":{"rendered":"<p>The <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/23\/solve-a-right-triangle\/\">previous post<\/a> gave a simple and accurate approximation for the smaller angle of a right triangle. Given a\u00a0right triangle with sides <em>a<\/em>,\u00a0<em>b<\/em>, and\u00a0<em>c<\/em>, where\u00a0<em>a<\/em> is the shortest side and\u00a0<em>c<\/em> is the hypotenuse, the angle opposite side <em>a<\/em> is approximately<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/oblique1.svg\" alt=\"A \\approx \\frac{3a}{b + 2c}\" width=\"87\" height=\"40\" \/><\/p>\n<p>in radians. The previous post worked in degrees, but here we&#8217;ll use radians.<\/p>\n<p>If the triangle is oblique rather than a right triangle, there an approximation for the angle <em>A<\/em> that doesn&#8217;t require inverse trig functions, though it does require square roots. The approximation is derived in [1] using the same series that is the basis of the approximation in the earlier post, the power series for 2 csc(<em>x<\/em>) + cot(<em>x<\/em>).<\/p>\n<p>For an oblique triangle, the approximation is<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/oblique2.svg\" alt=\"A \\approx \\frac{6 \\sqrt{(s - b)(s - c)}}{2\\sqrt{bc} + \\sqrt{s(s-a)}}\" width=\"179\" height=\"57\" \/><\/p>\n<p>where\u00a0<em>s<\/em> is the semiperimeter.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/oblique3.svg\" alt=\"s = \\frac{a + b + c}{2}\" width=\"101\" height=\"41\" \/><\/p>\n<p>For comparison, we can find the exact value of <em>A<\/em> using the law of cosines.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/oblique4.svg\" alt=\"a^2 = b^2 + c^2 - 2 bc \\cos A\" width=\"191\" height=\"18\" \/><\/p>\n<p>and so<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/oblique5.svg\" alt=\"A = \\cos^{-1}\\left(\\frac{b^2 + c^2 - a^2}{2bc}\\right)\" width=\"199\" height=\"49\" \/><\/p>\n<p>Here&#8217;s a little Python script to see how accurate the approximation is.<\/p>\n<pre>from math import sqrt, acos\r\n\r\ndef approx(a, b, c):\r\n    \"approximate the angle opposite a\"\r\n    s = (a + b + c)\/2\r\n    return 6*sqrt((s - b)*(s - c)) \/ (2*sqrt(b*c) + sqrt(s*(s - a)))\r\n\r\ndef exact(a, b, c):\r\n    \"exact value of the angle opposite a\"    \r\n    return acos((b**2 + c**2 - a**2)\/(2*b*c))\r\n\r\na, b, c = 6, 7, 12\r\nprint( approx(a, b, c) )\r\nprint( exact(a, b, c) )\r\n<\/pre>\n<p>This prints<\/p>\n<pre>0.36387538476776243\r\n0.36387760856668505\r\n<\/pre>\n<p>showing that in our example the approximation is good to five decimal places.<\/p>\n<p>[1] H. E. Stelson. Note on the approximate solution of an oblique triangle without tables. American Mathematical Monthly. Vol 56, No. 2 (February, 1949), pp. 84\u201395.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous post gave a simple and accurate approximation for the smaller angle of a right triangle. Given a\u00a0right triangle with sides a,\u00a0b, and\u00a0c, where\u00a0a is the shortest side and\u00a0c is the hypotenuse, the angle opposite side a is approximately in radians. The previous post worked in degrees, but here we&#8217;ll use radians. If the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-246984","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246984","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246984"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246984\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246984"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246984"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246984"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246983,"date":"2026-04-23T07:13:35","date_gmt":"2026-04-23T12:13:35","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246983"},"modified":"2026-04-23T11:26:29","modified_gmt":"2026-04-23T16:26:29","slug":"solve-a-right-triangle","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/23\/solve-a-right-triangle\/","title":{"rendered":"Simple approximation for solving a right triangle"},"content":{"rendered":"<p>Suppose you have a right triangle with sides\u00a0<em>a<\/em>,\u00a0<em>b<\/em>, and\u00a0<em>c<\/em>, where\u00a0<em>a<\/em> is the shortest side and\u00a0<em>c<\/em> is the hypotenuse. Then the following approximation from [1] for the angle <em>A<\/em>\u00a0opposite side <em>a<\/em> seems too simple and too accurate to be true. In degrees,<\/p>\n<p style=\"padding-left: 40px;\"><em>A<\/em> \u2248 <em>a<\/em> 172\u00b0 \/ (<em>b<\/em> + 2<em>c<\/em>).<\/p>\n<p>The approximation above only involves simple arithmetic. No trig functions. Not even a square root. It could be carried out with pencil and paper or even mentally. And yet it is surprisingly accurate.<\/p>\n<p>If we use the 3, 4, 5 triangle as an example, the exact value of the smallest angle is<\/p>\n<p style=\"padding-left: 40px;\"><em>A<\/em> = arctan(3\/4) \u00d7 180\u00b0\/\u03c0 \u2248 36.8699\u00b0<\/p>\n<p>and the approximate value is<\/p>\n<p style=\"padding-left: 40px;\"><em>A<\/em> \u2248\u00a0 3 \u00d7 172\u00b0 \/ (4 + 2\u00d75) = 258\u00b0\/7 \u2248 36.8571\u00b0,<\/p>\n<p>a difference of 0.0128\u00b0. When the angle is more acute the approximation is even better.<\/p>\n<h2>Derivation<\/h2>\n<p>Where does this magical approximation come from? It boils down to the series<\/p>\n<p style=\"padding-left: 40px;\">2 csc(<em>x<\/em>) + cot(<em>x<\/em>) = 3\/<em>x<\/em> +\u00a0<em>x<\/em>\u00b3\/60 +\u00a0<em>O<\/em>(<em>x<\/em><sup>4<\/sup>)<\/p>\n<p>where <em>x<\/em> is in radians. When <em>x<\/em> is small, \u00a0<em>x<\/em>\u00b3\/60 is extremely small and so we have<\/p>\n<p style=\"padding-left: 40px;\">2 csc(<em>x<\/em>) + cot(<em>x<\/em>) \u2248 3\/<em>x.<\/em><\/p>\n<p>Apply this approximation with csc(<em>x<\/em>) =\u00a0<em>c<\/em>\/<em>a<\/em> and cot(<em>x<\/em>) =\u00a0<em>b<\/em>\/<em>a<\/em>. and you have<\/p>\n<p style=\"padding-left: 40px;\"><em>x<\/em> \u2248 3<em>a<\/em>\/(<em>b<\/em> + 2<em>c<\/em>)<\/p>\n<p>in radians. Multiply by 180\u00b0\/\u03c0 to convert to degrees, and note that 540\/\u03c0 \u2248 172.<\/p>\n<h2>Discovery<\/h2>\n<p>It&#8217;s unmotivated to say &#8220;just expand 2 csc(<em>x<\/em>) + cot(<em>x<\/em>) in a series.&#8221; Where did\u00a0<em>that<\/em> come from?<\/p>\n<p>There&#8217;s a line in [1] that says &#8220;It can been seen, either from tables or from a consideration of power series that the radian measure of a small angle lies approximately one-third of the way from the sine to the tangent.&#8221; In other words<\/p>\n<p style=\"padding-left: 40px;\">3<em>x<\/em> \u2248 2 sin(<em>x<\/em>) + tan(<em>x<\/em>)<em>.<\/em><\/p>\n<p>You can verify that by adding the power series and noting that the cubic terms cancel out.<\/p>\n<p>But that&#8217;s just the beginning. The author then makes the leap to conjecturing that if the weighted <em>arithmetic<\/em> mean gives a good approximation, maybe the weighted\u00a0<em>harmonic<\/em> mean gives an even better approximation, and that leads to considering<\/p>\n<p style=\"padding-left: 40px;\">2 csc(<em>x<\/em>) + cot(<em>x<\/em>) \u2248 3\/<em>x.<\/em><\/p>\n<h2>Extension<\/h2>\n<p>See the <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/23\/solve-an-oblique-triangle\/\">next post<\/a> for an extension to oblique triangles. Not as simple, but based on the same trick.<\/p>\n<p>&nbsp;<\/p>\n<p>[1] J. S. Frame. Solving a right triangle without tables. The American Mathematical Monthly, Vol. 50, No. 10 (Dec., 1943), pp. 622-626<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Suppose you have a right triangle with sides\u00a0a,\u00a0b, and\u00a0c, where\u00a0a is the shortest side and\u00a0c is the hypotenuse. Then the following approximation from [1] for the angle A\u00a0opposite side a seems too simple and too accurate to be true. In degrees, A \u2248 a 172\u00b0 \/ (b + 2c). The approximation above only involves simple [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-246983","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246983","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246983"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246983\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246983"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246983"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246983"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246930,"date":"2026-04-21T15:14:40","date_gmt":"2026-04-21T20:14:40","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246930"},"modified":"2026-04-21T19:31:55","modified_gmt":"2026-04-22T00:31:55","slug":"an-ai-odyssey-part-4-astounding-coding-agents","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/21\/an-ai-odyssey-part-4-astounding-coding-agents\/","title":{"rendered":"An AI Odyssey, Part 4: Astounding Coding Agents"},"content":{"rendered":"<p>AI coding agents improved greatly last summer, and again last December-January. Here are my experiences since my <a href=\"https:\/\/www.johndcook.com\/blog\/2025\/10\/16\/experiences-with-gpt-5-codex\/\">last post<\/a> on the subject.<\/p>\n<p>The models feel subjectively much smarter. They can accomplish a much broader range of tasks. They seem to have a larger, more comprehensive in-depth view of the code base and what you are trying to accomplish. They can locate more details in obscure parts of the code related to the specific task at hand. By rough personal estimate, I would say that they helped me with 20% of my coding effort last August and about 60% now. Both of these figures may be below what is possible, I may not be using them to fullest capability.<\/p>\n<p>This being said, they are not a panacea. Sometimes they need help and direction to where to look for a problem. Sometimes they myopically look at the trees and don&#8217;t see the forest, don&#8217;t step back to see the big picture to understand something is wrong at a high level. Also they can overoptimize to the test harness. They can also generate code that is not conceptually consistent with existing code.<\/p>\n<p>They can also generate much larger amounts of code than necessary. Some warn that that this will lead to explosion of coding debt. On the other hand, you can also guide the coding agent to refactor and improve its own code&#8212;quickly. In one case I&#8217;ve gotten it to reduce the size of one piece of code to less than half its original size with no change in behavior.<\/p>\n<p>I use OpenAI Codex rather than Claude Code.\u00a0 I&#8217;m glad to hear <a href=\"https:\/\/steipete.me\/posts\/just-talk-to-it\">some technically credible people<\/a> think this a good choice. Though maybe I should try both.<\/p>\n<p>My work is a research project for which the code itself is the research product, so I can&#8217;t give specifications of everything in advance; writing the code itself is a process of discovery. Also, I want a code base that remains human-readable. So I am deeply involved in discussion with the coding agent that will sometimes go off to do a task for some period of time. It is not my desire to treat the agent as what some have called a <a href=\"https:\/\/www.danshapiro.com\/blog\/2026\/01\/the-five-levels-from-spicy-autocomplete-to-the-software-factory\/\">dark software factory<\/a>.<\/p>\n<p>Some say they fear that using a coding agent will result in forgetting how to write code without one. I have felt this, but from having exercised this muscle for such a very long time I don&#8217;t think it is a skill I would easily forget. The flip side of this argument is, you might even learn new coding idioms from observing what the coding agent writes, that is a good thing.<\/p>\n<p>Some say they haven&#8217;t written a line of code in weeks because the coding agent does it for them. I don&#8217;t think I&#8217;ll ever stop writing code, any more than I will stop scribbling random ideas on the back of an envelope, or typing out some number of lines of code by which I discover some new idea in real time while I am typing. Learning is multisensory.<\/p>\n<p>My hat\u2019s off to the developers who are able to keep many different agents in flight at the same time. Personally I have difficulty handling the cognitive load of thinking deeply about each one and doing a mental context switch across many agents. Though I am experimenting with running more than one agent so I can be doing something while another agent is working.<\/p>\n<p>I continue to be astounded by productivity gains in some situations. I recently added a new capability, which normally I think would&#8217;ve taken me two months to learn a whole new algorithmic method and library to use. With the coding agent, it took me four days to get this done, on the order of 10X productivity increase. Though admittedly things are not always that rosy.<\/p>\n<p>In short, the tools are getting better and better. I&#8217;m looking forward to what they will be like a few months or a year from now.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>AI coding agents improved greatly last summer, and again last December-January. Here are my experiences since my last post on the subject. The models feel subjectively much smarter. They can accomplish a much broader range of tasks. They seem to have a larger, more comprehensive in-depth view of the code base and what you are [&hellip;]<\/p>\n","protected":false},"author":8,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[260,238,16],"tags":[322,274,323],"class_list":["post-246930","post","type-post","status-publish","format-standard","hentry","category-ai","category-productivity","category-software-development","tag-codex","tag-openai","tag-software-tools"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246930","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246930"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246930\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246930"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246930"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246930"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246980,"date":"2026-04-20T08:50:33","date_gmt":"2026-04-20T13:50:33","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246980"},"modified":"2026-04-20T08:50:33","modified_gmt":"2026-04-20T13:50:33","slug":"newton-diameter-quintic","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/20\/newton-diameter-quintic\/","title":{"rendered":"More on Newton&#8217;s diameter theorem"},"content":{"rendered":"<p>A few days ago I wrote a post on <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/16\/newton-diameters\/\">Newton&#8217;s diameter theorem<\/a>. The theorem says to plot the curve formed by the solutions to <em>f<\/em>(<em>x<\/em>, <em>y<\/em>) = 0 where <em>f<\/em> is a polynomial in <em>x<\/em> and <em>y<\/em> of degree <em>n<\/em>. Next plot several parallel lines that cross the curve at <em>n<\/em> points and find the centroid of the intersections on each line. Then the centroids will fall on a line.<\/p>\n<p>The previous post contained an illustration using a cubic polynomial and three evenly spaced parallel lines. This post uses a fifth degree polynomial, and shows that the parallel lines need not be evenly spaced.<\/p>\n<p>In this post<\/p>\n<p style=\"padding-left: 40px;\"><em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) = <em>y<\/em>\u00b3 + <em>y<\/em> \u2212 <em>x<\/em> (<em>x<\/em> + 1) (<em>x<\/em> + 2) (<em>x<\/em> \u2212 3) (<em>x<\/em> \u2212 2).<\/p>\n<p>Here&#8217;s an example of three lines that each cross the curve five times.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/hyperelliptic_newton1.png\" width=\"575\" height=\"578\" \/><\/p>\n<p>The lines are\u00a0<em>y<\/em> =\u00a0<em>x<\/em> +\u00a0<em>k<\/em> where\u00a0<em>k<\/em> = 0.5, \u22120.5, and \u22123. The coordinates of the centroids are (0.4, 0.9), (0.4, -0.1), and (0.4, -2.6).<\/p>\n<p>And to show that the requirement that the lines cross five times is necessary, here&#8217;s a plot where one of the parallel lines only crosses three times. The top line is now\u00a0<em>y<\/em> =\u00a0<em>x<\/em> + 2 and the centroid on the top line moved to (0.0550019, 2.055).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/hyperelliptic_newton2.png\" width=\"575\" height=\"578\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A few days ago I wrote a post on Newton&#8217;s diameter theorem. The theorem says to plot the curve formed by the solutions to f(x, y) = 0 where f is a polynomial in x and y of degree n. Next plot several parallel lines that cross the curve at n points and find the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-246980","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246980","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246980"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246980\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246980"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246980"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246980"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246976,"date":"2026-04-18T09:49:27","date_gmt":"2026-04-18T14:49:27","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246976"},"modified":"2026-04-18T10:28:31","modified_gmt":"2026-04-18T15:28:31","slug":"qlora","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/18\/qlora\/","title":{"rendered":"Gaussian distributed weights for LLMs"},"content":{"rendered":"<p>The <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/17\/fp4\/\">previous post<\/a> looked at the <strong>FP4<\/strong> 4-bit floating point format. This post will look at another 4-bit floating point format, <strong>NF4<\/strong>, and higher precision analogs. NF4 and FP4 are common bitsandbytes 4-bit data types. If you download LLM weights from Hugging Face quantized to four bits, the weights might be in NF4 or FP4 format. Or maybe some other format: there&#8217;s a surprising amount of variety in how 4-bit numbers are implemented.<\/p>\n<h2>Why NF4<\/h2>\n<p>LLM parameters have a roughly Gaussian distribution, and so evenly spaced numeric values are not ideal for parameters. Instead, you&#8217;d like numbers that are closer together near 0. <\/p>\n<p>The FP4 floating point numbers, described in the previous post, are spaced 0.5 apart for small values, and the larger values are spaced 1 or 2 apart. That&#8217;s hardly a Gaussian distribution, but it&#8217;s closer to Gaussian than a uniform distribution would be. NF4 deliberately follows more of a Gaussian distribution.<\/p>\n<h2>QLoRA<\/h2>\n<p>The QLoRA formats [1], unlike FP4, are not analogs of IEEE numbers. The bits are not interpreted as sign, exponent, and mantissa, but rather as integers to be used as indexes. An NF<em>n<\/em> number is an index into a list of 2<sup><em>n<\/em><\/sup> real numbers with Gaussian spacing. To put it another way, the numbers represented by NF<em>n<\/em> have uniformly distributed <em>z<\/em>-scores. <\/p>\n<p>That makes sense at a high level, but the paper [1] is hard to follow in detail. It says<\/p>\n<blockquote><p>More formally, we estimate the 2<sup><em>k<\/em><\/sup> values <em>q<\/em><sub><em>i<\/em><\/sub> of the data type as follows:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/qlora.svg\" alt=\"q_i = \\frac{1}{2}\\left(Q_X\\left(\\frac{i}{2^k + 1}\\right) + Q_X\\left(\\frac{i+1}{2^k + 1} \\right) \\right)\" width=\"293\" height=\"48\" \/><\/p>\n<p>where <em>Q<\/em><sub><em>X<\/em><\/sub>(\u00b7) is the quantile function of the standard normal distribution <em>N<\/em>(0, 1).<\/p><\/blockquote>\n<p>The paper doesn&#8217;t give the range of <em>i<\/em> but it says there are 2<sup><em>k<\/em><\/sup> values, implying that <em>i<\/em> runs from 0 to 2<sup><em>k<\/em><\/sup> \u22121 or from 1 to 2<sup><em>k<\/em><\/sup>. Either way runs into infinite values since <em>Q<\/em>(0) = \u2212\u221e and <em>Q<\/em>(1) = \u221e. We could avoid infinities by letting <em>i<\/em> run from 1 to 2<sup><em>n<\/em><\/sup> \u2212 1.<\/p>\n<p>The next sentence is puzzling.<\/p>\n<blockquote><p>A problem for a symmetric k-bit quantization is that this approach does not have an exact representation of zero, which is an important property to quantize padding and other zero-valued elements with no error.<\/p><\/blockquote>\n<p>I understand the desire to represent 0 exactly, but the equation above has an exact representation of 0 when <em>i<\/em> = 2<sup><em>n<\/em> \u2212 1<\/sup>. Perhaps the authors had in mind that <em>i<\/em> takes on the values \u00bd, 1 + \u00bd, 2 + \u00bd, \u2026, 2<sup><em>n<\/em><\/sup> \u2212 \u00bd. This would be reasonable, but a highly unusual use of notation. It seems that the real problem is not the lack of a representation of 0 but an unused index, with <em>i<\/em> running from 1 to 2<sup><em>n<\/em><\/sup> \u2212 1.<\/p>\n<p>To be fair, the first sentence quoted above says &#8220;we <em>estimate<\/em> the 2<sup><em>k<\/em><\/sup> values &hellip;&#8221; and so the equation above may not be intended as a definition but as motivation for the actual definition.<\/p>\n<h2>Reproducing NF4<\/h2>\n<p>The authors give a procedure for using 2<sup><em>n<\/em><\/sup> values of <em>i<\/em> and obtaining an exact representation of 0, and they give a list of NF4 values in Appendix E. I was not able to get the two to match. I implemented a few possible interpretations of the procedure described in the paper, and each approximates the list of values in the appendix, but not closely.<\/p>\n<p>The following code, written with the help of ChatGPT, reverse engineers the NF4 values to 8 decimal places, i.e. to the precision of a 32-bit floating point number.<\/p>\n<pre>\r\nfrom scipy.stats import norm\r\n\r\nQ = norm.ppf\r\n\r\n\u03b1  = 0.9677083\r\nZ  = Q(\u03b1)\r\n\u03b41 = (\u03b1 - 0.5)\/7\r\n\u03b42 = (\u03b1 - 0.5)\/8\r\n\r\nq = [0]*16\r\nfor i in range(7):\r\n    q[i] = -Q(\u03b1 - i*\u03b41)\/Z\r\nfor i in range(8):\r\n    q[i+8] = Q(0.5 + (i+1)*\u03b42)\/Z\r\n    \r\n# Values given in Appendix E\r\nNF4 = [\r\n    -1.0,\r\n    -0.6961928009986877,\r\n    -0.5250730514526367,\r\n    -0.39491748809814453,\r\n    -0.28444138169288635,\r\n    -0.18477343022823334,\r\n    -0.09105003625154495,\r\n    0.0,\r\n    0.07958029955625534,\r\n    0.16093020141124725,\r\n    0.24611230194568634,\r\n    0.33791524171829224,\r\n    0.44070982933044434,\r\n    0.5626170039176941,\r\n    0.7229568362236023,\r\n    1.0\r\n]\r\n\r\n# Compare \r\nfor i in range(16):\r\n    print(i, NF4[i] - q[i])\r\n<\/pre>\n<p>The magic number \u03b1 = 0.9677083 is a mystery. I asked ChatGPT to look into this further, and it said that bitsandbytes uses \u03b1 = 929\/960 = 0.9677083333333333. When I use this value for \u03b1 the precision is about the same, which is fine. However, the values in the paper were given to 16 decimal places, so I thought it might be able to match the values to more precision.<\/p>\n<p>Quibbles over the exact values of NF4 aside, the NF4 format works well in practice. Models. quantized to 4 bits using NF4 perform better than models quantized to other 4-bit formats on some benchmarks.<\/p>\n<h2>Related posts<\/h2>\n<ul>\n<li class='link'><a href='https:\/\/www.johndcook.com\/blog\/2009\/04\/06\/anatomy-of-a-floating-point-number\/'>Anatomy of a floating point number<\/a><\/li>\n<li class='link'><a href='https:\/\/www.johndcook.com\/blog\/2018\/04\/11\/anatomy-of-a-posit-number\/'>Anatomy of a posit number<\/a><\/li>\n<li class='link'><a href='https:\/\/www.johndcook.com\/blog\/2018\/11\/15\/bfloat16\/'>Half precision numbers<\/a><\/li>\n<\/ul>\n<p>[1] QLoRA: Efficient Finetuning of Quantized LLMs by Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer. <a href=\"https:\/\/arxiv.org\/abs\/2305.14314\">https:\/\/arxiv.org\/abs\/2305.14314<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The previous post looked at the FP4 4-bit floating point format. This post will look at another 4-bit floating point format, NF4, and higher precision analogs. NF4 and FP4 are common bitsandbytes 4-bit data types. If you download LLM weights from Hugging Face quantized to four bits, the weights might be in NF4 or FP4 [&hellip;]<\/p>\n","protected":false},"author":10,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[260],"tags":[271],"class_list":["post-246976","post","type-post","status-publish","format-standard","hentry","category-ai","tag-number-systems"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246976","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246976"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246976\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246976"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246976"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246976"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246975,"date":"2026-04-17T20:08:54","date_gmt":"2026-04-18T01:08:54","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246975"},"modified":"2026-04-20T01:58:35","modified_gmt":"2026-04-20T06:58:35","slug":"fp4","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/17\/fp4\/","title":{"rendered":"4-bit floating point FP4"},"content":{"rendered":"<p>In ancient times, floating point numbers were stored in 32 bits. Then somewhere along the way 64 bits became standard. The C programming language retains the ancient lore, using <code>float<\/code> to refer to a 32-bit floating point number and <code>double<\/code> to refer to a floating point number with double the number of bits. Python simply uses <code>float<\/code> to refer to the most common floating point format, which C calls <code>double<\/code>.<\/p>\n<p>Programmers were grateful for the move from 32-bit floats to 64-bit floats. It doesn&#8217;t hurt to have more precision, and some numerical problems go away when you go from 32-bits to 64-bits. (Though not all. Something I&#8217;ve written about numerous times.)<\/p>\n<p>Neural networks brought about something extraordinary: demand for floating point numbers with <em>less<\/em> precision. These networks have an enormous number of parameters, and its more important to fit more parameters into memory than it is to have higher precision parameters. Instead of double precision (64 bits) developers wanted <em>half<\/em> precision (16 bits), or even less, such as FP8 (8 bits) or FP4 (4 bits). This post will look at 4-bit floating point numbers.<\/p>\n<p>Why even bother with floating point numbers when you don&#8217;t need much precision? Why not use integers? For example, with four bits you could represent the integers 0, 1, 2, \u2026, 15. You could introduce a bias, subtracting say 7 from each value, so your four bits represent \u22127 through 8. Turns out it&#8217;s useful to have a more dynamic range.<\/p>\n<h2>FP4<\/h2>\n<p>Signed 4-bit floating point numbers in FP4 format use the first bit to represent the sign. The question is what to do with the remaining three bits. The notation E<em>x<\/em>M<i>y<\/i>\u00a0denotes a format with\u00a0<em>x<\/em> exponent bits and\u00a0<em>y<\/em> mantissa bits. In the context of signed 4-bit numbers,<\/p>\n<p style=\"padding-left: 40px;\"><em>x<\/em> +\u00a0<em>y<\/em> = 3<\/p>\n<p>but in other contexts the sum could be larger. For example, for an 8-bit signed float,\u00a0<em>x<\/em> +\u00a0<em>y<\/em> = 7.<\/p>\n<p>For 4-bit signed floats we have four possibilities: E3M0, E2M1, E1M2, and E0M3. All are used somewhere, but E2M1 is the most common and is supported in Nvidia hardware.<\/p>\n<p>A number with sign bit\u00a0<em>s<\/em>, exponent\u00a0<em>e<\/em>, and mantissa\u00a0<em>m<\/em> has the value<\/p>\n<p style=\"padding-left: 40px;\">(\u22121)<sup><em>s<\/em><\/sup> 2<sup><em>e<\/em>\u2212<em>b<\/em><\/sup> (1 + <em>m<\/em>\/2)<\/p>\n<p>where <em>b<\/em> is the bias. The purpose of the bias is to allow positive and negative exponents without using signed numbers for\u00a0<em>e<\/em>. So, for example, if\u00a0<em>b<\/em> = 1 and <em>e<\/em> = 1, 2, or 3 then the exponent part 2<sup><em>e<\/em>\u2212<em>b<\/em><\/sup> can represent 1, 2, or 4.<\/p>\n<p>The bias impacts the range of possible numbers but not their relative spacing. For any value of bias <em>b<\/em>, the E3M0 format is all exponent, no mantissa, and so its possible values are uniformly distributed on a log scale. The E0M3 format is all mantissa, so its values are uniformly distributed on a linear scale. The E1M2 and E2M1 formats are unevenly spaced on both log and linear scales.<\/p>\n<p>There is an exception to the expression above for converting (<em>s<\/em>,\u00a0<em>e<\/em>,\u00a0<em>m<\/em>) into a real number when <em>e<\/em> = 0. In that case,\u00a0<em>m<\/em> = 0 represents 0 and\u00a0<em>m<\/em> = 1 represents \u00bd.<\/p>\n<h2>Table of values<\/h2>\n<p>Since there are only 16 possible FP4 numbers, it&#8217;s possible to list them all. Here is a table for the E2M1 format.<\/p>\n<pre>Bits s exp m  Value\r\n-------------------\r\n0000 0  00 0     +0\r\n0001 0  00 1   +0.5\r\n0010 0  01 0     +1\r\n0011 0  01 1   +1.5\r\n0100 0  10 0     +2\r\n0101 0  10 1     +3\r\n0110 0  11 0     +4\r\n0111 0  11 1     +6\r\n1000 1  00 0     -0\r\n1001 1  00 1   -0.5\r\n1010 1  01 0     -1\r\n1011 1  01 1   -1.5\r\n1100 1  10 0     -2\r\n1101 1  10 1     -3\r\n1110 1  11 0     -4\r\n1111 1  11 1     -6\r\n<\/pre>\n<p>Note that even in this tiny floating point format, there are two zeros, +0 and \u22120, just like full precision floats. More on that <a href=\"https:\/\/www.johndcook.com\/blog\/2010\/06\/15\/why-computers-have-signed-zero\/\">here<\/a>.<\/p>\n<h2>Pychop library<\/h2>\n<p>The Python library <a href=\"https:\/\/github.com\/inexascale\/pychop\">Pychop<\/a> emulates a wide variety of reduced-precision floating point formats. Here is the code that used Pychop to create the table above.<\/p>\n<pre>import pychop\r\n\r\n# Pull the format metadata from Pychop.\r\nspec = pychop.MX_FORMATS[\"mxfp4_e2m1\"]\r\nassert (spec.exp_bits, spec.sig_bits) == (2, 1)\r\n\r\ndef e2m1_value(s: int, e: int, m: int) -&gt; float:\r\n    sign = -1.0 if s else 1.0\r\n\r\n    # Subnormal \/ zero\r\n    if e == 0:\r\n        return sign * (m \/ 2.0)\r\n\r\n    # Normal\r\n    return sign * (2.0 ** (e - 1)) * (1.0 + m \/ 2.0)\r\n\r\ndef display_value(bits: int, x: float) -&gt; str:\r\n    if bits == 0b0000:\r\n        return \"+0\"\r\n    if bits == 0b1000:\r\n        return \"-0\"\r\n    return f\"{x:+g}\"\r\n\r\nrows = []\r\nfor bits in range(16):\r\n    s = (bits &gt;&gt; 3) &amp; 0b1\r\n    e = (bits &gt;&gt; 1) &amp; 0b11\r\n    m = bits &amp; 0b1\r\n    x = e2m1_value(s, e, m)\r\n\r\n    rows.append(\r\n        {\r\n            \"Bits\": f\"{bits:04b}\",\r\n            \"s\": s,\r\n            \"exp_bits\": f\"{e:02b}\",\r\n            \"m\": m,\r\n            \"Value\": display_value(bits, x),\r\n        }\r\n    )\r\n\r\n# Pretty-print the table.\r\nheader = f\"{'Bits':&lt;4} {'s':&gt;1} {'exp':&gt;3} {'m':&gt;1} {'Value':&gt;6}\"\r\nprint(header)\r\nprint(\"-\" * len(header))\r\nfor row in rows:\r\n    print(\r\n        f\"{row['Bits']:&lt;4} \" f\"{row['s']:&gt;1} \"\r\n        f\"{row['exp_bits']:&gt;3} \"\r\n        f\"{row['m']:&gt;1} \"\r\n        f\"{row['Value']:&gt;6}\"\r\n    )\r\n<\/pre>\n<h2>Other formats<\/h2>\n<p>FP4 isn&#8217;t the only 4-bit floating point format. There&#8217;s a surprisingly large number of formats in use. I intend to address another format my next post.<\/p>\n<p><b>Update<\/b>: See the <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/18\/qlora\/\">next post<\/a> for a discussion of NF4, a format whose representable numbers more closely matches the distribution of LLM weights.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In ancient times, floating point numbers were stored in 32 bits. Then somewhere along the way 64 bits became standard. The C programming language retains the ancient lore, using float to refer to a 32-bit floating point number and double to refer to a floating point number with double the number of bits. Python simply [&hellip;]<\/p>\n","protected":false},"author":10,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[260],"tags":[271],"class_list":["post-246975","post","type-post","status-publish","format-standard","hentry","category-ai","tag-number-systems"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246975"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246975\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246973,"date":"2026-04-16T07:02:13","date_gmt":"2026-04-16T12:02:13","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246973"},"modified":"2026-04-20T08:51:49","modified_gmt":"2026-04-20T13:51:49","slug":"newton-diameters","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/16\/newton-diameters\/","title":{"rendered":"Newton diameters"},"content":{"rendered":"<p>Let\u00a0<em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) be an\u00a0<em>n<\/em>th degree polynomial in\u00a0<em>x<\/em> and\u00a0<em>y<\/em>. In general, a straight line will cross the zero set of\u00a0<em>f<\/em> in\u00a0<em>n<\/em> locations [1].<\/p>\n<p>Newton defined a <strong>diameter<\/strong> to be any line that crosses the zero set of\u00a0<em>f<\/em> exactly\u00a0<em>n<\/em> times. If<\/p>\n<p style=\"padding-left: 40px;\"><em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) =\u00a0<em>x<\/em>\u00b2 +\u00a0<em>y<\/em>\u00b2 \u2212 1<\/p>\n<p>then the zero set of\u00a0<em>f<\/em> is a circle and diameters of the circle in the usual sense are diameters in Newton&#8217;s sense. But Newton&#8217;s notion of diameter is more general, including lines the cross the circle without going through the center.<\/p>\n<p>Newton&#8217;s theorem of diameters says that if you take several parallel diameters (in his sense of the word), the centroids of the intersections of each diameter with the curve <em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) = 0 all line on a line.<\/p>\n<p>To illustrate this theorem, let&#8217;s look at the elliptic curve<\/p>\n<p style=\"padding-left: 40px;\"><em>y<\/em>\u00b2 = <em>x<\/em>\u00b3 \u2212 2<em>x<\/em> + 1,<\/p>\n<p>i.e. the zeros of <em>f<\/em>(<em>x<\/em>,\u00a0<em>y<\/em>) = <em>y<\/em>\u00b2 \u2212 (<em>x<\/em>\u00b3 \u2212 2<em>x<\/em> + 1). This is a third degree curve, and so in general a straight line will cross the curve three times [2].<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/newton_diameters2.png\" width=\"640\" height=\"480\" \/><\/p>\n<p>The orange, green, and red lines are parallel, each intersecting the blue elliptic curve three times. The dot on each line is the centroid of the intersection points, the center of mass if you imagine each intersection to be a unit point mass. The centroids all lie on a line, a vertical line in this example though in general the line could have any slope.<\/p>\n<p>I hadn&#8217;t seen this theorem until I ran across it recently when skimming [3]. Search results suggest the theorem isn&#8217;t widely known, which is surprising for a result that goes back to Newton.<\/p>\n<p><strong>Update<\/strong>: See <a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/20\/newton-diameter-quintic\/\">this post<\/a> for another illustration using a fifth degree polynomial.<\/p>\n<h2>Related posts<\/h2>\n<ul>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2019\/02\/21\/what-is-an-elliptic-curve\/\">What is an elliptic curve?<\/a><\/li>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2020\/05\/27\/fundamental-theorem-of-algebra\/\">The fundamental theorem of algebra<\/a><\/li>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2022\/10\/28\/where-quadratic-curves-intersect\/\">Finding where two quadratic curves intersect<\/a><\/li>\n<\/ul>\n<p>[1] B\u00e9zout&#8217;s theorem says a curve of degree <em>m<\/em> and a curve of degee <em>n<\/em> will always intersect in <em>mn<\/em> points. But that includes complex roots, adds a line at infinity, and counts intersections with multiplicity. So a line, a curve of degree 1, will intersect a curve of degree <em>n<\/em> at <em>n<\/em> points in this extended sense.<\/p>\n<p>[2] See the description of B\u00e9zout&#8217;s theorem in the previous footnote. In the elliptic curve example, the parallel lines meet at a point at infinity. A line that misses the closed component of the elliptic curve and only passes through the second component has 1 real point of intersection but there would be 2 more if we were working in \u2102\u00b2 rather than \u211d\u00b2.<\/p>\n<p>In algebraic terms, the system of equations<\/p>\n<p style=\"padding-left: 40px;\"><em>y<\/em>\u00b2 = <em>x<\/em>\u00b3 \u2212 2<em>x<\/em> + 1<br \/>\n3<em>y<\/em> = 2<em>x<\/em> + <em>k<br \/>\n<\/em><\/p>\n<p>has three real solutions for small values of <em>k<\/em>, but for sufficiently large values of |<em>k<\/em>| two of the solutions will be complex.<\/p>\n<p>[3] Mathematics: Its Content, Methods, and Meaning. Edited by A. D. Aleksandrov, A. N. Kolmogorov, and M. A. Lavrent&#8217;ev. Volume 1.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let\u00a0f(x,\u00a0y) be an\u00a0nth degree polynomial in\u00a0x and\u00a0y. In general, a straight line will cross the zero set of\u00a0f in\u00a0n locations [1]. Newton defined a diameter to be any line that crosses the zero set of\u00a0f exactly\u00a0n times. If f(x,\u00a0y) =\u00a0x\u00b2 +\u00a0y\u00b2 \u2212 1 then the zero set of\u00a0f is a circle and diameters of the [&hellip;]<\/p>\n","protected":false},"author":10,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-246973","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246973","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246973"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246973\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246973"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246973"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246973"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246970,"date":"2026-04-14T09:08:36","date_gmt":"2026-04-14T14:08:36","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246970"},"modified":"2026-04-14T12:02:08","modified_gmt":"2026-04-14T17:02:08","slug":"intersecting-spheres-and-gps","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/14\/intersecting-spheres-and-gps\/","title":{"rendered":"Intersecting spheres and GPS"},"content":{"rendered":"<p>If you know the distance <em>d<\/em> to a satellite, you can compute a circle of points that passes through your location. That&#8217;s because you&#8217;re at the intersection of two spheres\u2014the earth&#8217;s surface and a sphere of radius <em>d<\/em> centered on the satellite\u2014and the intersection of two spheres is a circle. Said another way, one observation of a satellite determines a circle of possible locations.<\/p>\n<p>If you know the distance to a second satellite as well, then you can find two circles that contain your location. The two circles intersect at two points, and you know that you&#8217;re at one of two possible positions. If you know your approximate position, you may be able to rule out one of the intersection points.<\/p>\n<p>If you know the distance to three different satellites, now you know three circles that you&#8217;re standing on, and the third circle will only pass through one of the two points determined by the first two satellites. Now you know exactly where you are.<\/p>\n<p>Knowing the distance to more satellites is even better. In theory additional observations are redundant but harmless. In practice, they let you partially cancel out inevitable measurement errors.<\/p>\n<p>If you&#8217;re not on the earth&#8217;s surface, you&#8217;re still at the intersection of\u00a0<em>n<\/em> spheres if you know the distance to\u00a0<em>n<\/em> satellites. If you&#8217;re in an airplane, or on route to the moon, the same principles apply.<\/p>\n<h2>Errors and corrections<\/h2>\n<p>How do you know the distance to a satellite? The satellite can announce what time it is by its clock, then when you receive the announcement you compare it to the time by your clock. The difference between the two times tells you how long the radio signal traveled. Multiply by the speed of light and you have the distance.<\/p>\n<p>However, your clock will probably not be exactly synchronized with the satellite clock. Observing a fourth satellite can fix the problem of your clock not being synchronized with the satellite clocks. But it doesn&#8217;t fix the more subtle problems of special relativity and general relativity. See <a href=\"https:\/\/perthirtysix.com\/how-does-gps-work\">this post<\/a> by Shri Khalpada for an accessible discussion of the physics.<\/p>\n<h2>Numerical computation<\/h2>\n<p>Each distance measurement gives you an equation:<\/p>\n<p style=\"padding-left: 40px;\">|| <em>x<\/em> &#8211; <em>s<\/em><sub><em>i<\/em><\/sub> || = <em>d<\/em><sub><em>i<\/em><\/sub><\/p>\n<p>where <em>s<\/em><sub><em>i<\/em><\/sub> is the location of the <em>i<\/em>th satellite and <em>d<\/em><sub><em>i<\/em><\/sub> is your distance to that satellite. If you square both sides of the equation, you have a quadratic equation. You have to solve a system of nonlinear equations, and yet there is a way to transform the problem into solving linear equations, i.e. using linear algebra. See <a href=\"https:\/\/www.cambridge.org\/core\/journals\/anziam-journal\/article\/note-on-computing-the-intersection-of-spheres-in-mathbbrn\/D15FB22917024962409980AC7D3C086D\">this article<\/a> for details.<\/p>\n<h2>Related posts<\/h2>\n<ul>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2025\/12\/04\/the-navigational-triangle\/\">The navigation triangle<\/a><\/li>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2023\/03\/07\/duttons\/\">Dutton&#8217;s Navigation and Piloting<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>If you know the distance d to a satellite, you can compute a circle of points that passes through your location. That&#8217;s because you&#8217;re at the intersection of two spheres\u2014the earth&#8217;s surface and a sphere of radius d centered on the satellite\u2014and the intersection of two spheres is a circle. Said another way, one observation [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[224,312],"class_list":["post-246970","post","type-post","status-publish","format-standard","hentry","category-math","tag-geometry","tag-navigation"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246970","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246970"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246970\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246970"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246970"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246970"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246968,"date":"2026-04-14T07:19:42","date_gmt":"2026-04-14T12:19:42","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246968"},"modified":"2026-04-14T17:04:28","modified_gmt":"2026-04-14T22:04:28","slug":"artz-parabola","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/14\/artz-parabola\/","title":{"rendered":"Finding a parabola through two points with given slopes"},"content":{"rendered":"<p>The Wikipedia article on modern triangle geometry has an image labeled &#8220;Artzt parabolas&#8221; with no explanation.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium\" src=\"https:\/\/www.johndcook.com\/artz_triangle.png\" width=\"330\" height=\"247\" \/><\/p>\n<p>A quick search didn&#8217;t turn up anything about Artzt parabolas [1], but apparently the parabolas go through pairs of vertices with tangents parallel to the sides.<\/p>\n<p>The general form of a conic section is<\/p>\n<p style=\"padding-left: 40px;\"><em>ax<\/em>\u00b2 + <em>bxy<\/em> + <em>cy<\/em>\u00b2 + <em>dx<\/em> + <em>ey<\/em> + <em>f<\/em> = 0<\/p>\n<p>and the constraint\u00a0<em>b<\/em>\u00b2 = 4<em>ac<\/em> means the conic will be a parabola.<\/p>\n<p>We have 6 parameters, each determined only up to a scaling factor; you can multiply both sides by any non-zero constant and still have the same conic. So a general conic has 5 degrees of freedom, and the parabola condition <em>b<\/em>\u00b2 = 4<em>ac<\/em> takes us down to 4. Specifying two points that the parabola passes through takes up 2 more degrees of freedom, and specifying the slopes takes up the last two. So it&#8217;s plausible that there is a unique solution to the problem.<\/p>\n<p>There is indeed a solution, unique up to scaling the parameters. The following code finds parameters of a parabola that passes through (<em>x<\/em><sub><em>i<\/em><\/sub>, <em>y<\/em><sub><em>i<\/em><\/sub>) with slope <em>m<\/em><sub><em>i<\/em><\/sub> for <em>i<\/em> = 1, 2.<\/p>\n<pre>def solve(x1, y1, m1, x2, y2, m2):\r\n    \r\n    \u0394x = x2 - x1\r\n    \u0394y = y2 - y1\r\n    \u03bb = 4*(\u0394x*m1 - \u0394y)*(\u0394x*m2 - \u0394y)\/(m1 - m2)**2\r\n    k = x2*y1 - x1*y2\r\n\r\n    a = \u0394y**2 + \u03bb*m1*m2\r\n    b = -2*\u0394x*\u0394y - \u03bb*(m1 + m2)\r\n    c = \u0394x**2 + \u03bb\r\n    d =  2*k*\u0394y + \u03bb*(m1*y2 + m2*y1 - m1*m2*(x1 + x2))\r\n    e = -2*k*\u0394x + \u03bb*(m1*x1 + m2*x2 - y1 - y2)\r\n    f = k**2 + \u03bb*(m1*x1 - y1)*(m2*x2 - y2)\r\n\r\n    return (a, b, c, d, e, f)\r\n<\/pre>\n<p>[1] The page said &#8220;Artz&#8221; when I first looked at it, but it has since been corrected to &#8220;Artzt&#8221;. Maybe I didn&#8217;t find anything because I was looking for the wrong spelling.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Wikipedia article on modern triangle geometry has an image labeled &#8220;Artzt parabolas&#8221; with no explanation. A quick search didn&#8217;t turn up anything about Artzt parabolas [1], but apparently the parabolas go through pairs of vertices with tangents parallel to the sides. The general form of a conic section is ax\u00b2 + bxy + cy\u00b2 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[224],"class_list":["post-246968","post","type-post","status-publish","format-standard","hentry","category-math","tag-geometry"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246968","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246968"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246968\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246968"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246968"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246968"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}},{"id":246966,"date":"2026-04-13T09:33:16","date_gmt":"2026-04-13T14:33:16","guid":{"rendered":"https:\/\/www.johndcook.com\/blog\/?p=246966"},"modified":"2026-04-13T20:28:23","modified_gmt":"2026-04-14T01:28:23","slug":"the-smallest-math-library","status":"publish","type":"post","link":"https:\/\/www.johndcook.com\/blog\/2026\/04\/13\/the-smallest-math-library\/","title":{"rendered":"Mathematical minimalism"},"content":{"rendered":"<p>Andrzej Odrzywolek recently posted an article on <a href=\"https:\/\/arxiv.org\/abs\/2603.21852v2\">arXiv<\/a> showing that you can obtain all the elementary functions from just the function<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/eml.svg\" alt=\"\\operatorname{eml}(x,y) = \\exp(x) - \\log(y)\" width=\"224\" height=\"18\" \/><\/p>\n<p>and the constant 1. The following equations, taken from the paper&#8217;s <a href=\"https:\/\/arxiv.org\/src\/2603.21852v2\/anc\/SupplementaryInformation.pdf\">supplement<\/a>, show how to bootstrap addition, subtraction, multiplication, and division from the eml function.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter\" style=\"background-color: white;\" src=\"https:\/\/www.johndcook.com\/elm.svg\" alt=\"\\begin{align*} \\exp(z) &amp;\\mapsto \\operatorname{eml}(z,1) \\\\ \\log(z) &amp;\\mapsto \\operatorname{eml}(1,\\exp(\\operatorname{eml}(1,z))) \\\\ x - y &amp;\\mapsto \\operatorname{eml}(\\log(x),\\exp(y)) \\\\ -z &amp;\\mapsto (\\log 1) - z \\\\ x + y &amp;\\mapsto x - (-y) \\\\ 1\/z &amp;\\mapsto \\exp(-\\log z) \\\\ x \\cdot y &amp;\\mapsto \\exp(\\log x + \\log y) \\end{align*}\" width=\"264\" height=\"193\" \/><\/p>\n<p>See the paper and supplement for how to obtain constants like \u03c0 and functions like square and square root, as well as the standard circular and hyperbolic functions.<\/p>\n<h2>Related posts<\/h2>\n<ul>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2021\/01\/05\/bootstrapping-math-library\/\">Bootstrapping a small math library<\/a><\/li>\n<li class=\"link\"><a href=\"https:\/\/www.johndcook.com\/blog\/2026\/04\/06\/tofolli-gates\/\">Toffoli gates are all you need<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Andrzej Odrzywolek recently posted an article on arXiv showing that you can obtain all the elementary functions from just the function and the constant 1. The following equations, taken from the paper&#8217;s supplement, show how to bootstrap addition, subtraction, multiplication, and division from the eml function. See the paper and supplement for how to obtain [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"class_list":["post-246966","post","type-post","status-publish","format-standard","hentry","category-math"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246966","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/comments?post=246966"}],"version-history":[{"count":0,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/posts\/246966\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/media?parent=246966"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/categories?post=246966"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.johndcook.com\/blog\/wp-json\/wp\/v2\/tags?post=246966"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}]