Prime powers

One of my favorite projects for AI large language models is to identify prime powers. I consistently don’t get the right answer on the first try.

Here is my latest request for OpenAI’s ChatGPT 4

what are the first 10 prime powers. a prime power is a prime number raised to an exponent of 2 or more. use an “is prime power” function

I added the suggestion to use an “is prime power” function because, in the past, queries without that suggestion generated obviously incorrect results. This time the error was more subtle. I didn’t notice it at first.

After that, I asked ChatGPT 4 to show me the factorization of the first 25 prime powers past 78120. (A few days ago, I had requested a list of the first 100 prime powers. 78125 is the 100th. I wanted to extend the list.)

I was surprised with this comment at the end of its answer.

It appears there was a mistake in interpreting some numbers as prime powers; specifically, those represented as \(x^2\) where \(x\) is not prime. This discrepancy arises from the function initially designed to identify prime powers without checking if the base is prime for squared values.

It had the insight to see a mistake without my pointing it out. When I asked to recreate the list, it updated the offending function and gave correct results.

This remark is an example of ChatGPT 4’s ability to understand what it is creating. It’s a higher order thinking process to create the code and then evaluate its results autonomously.

I’ve never trusted that current AI models would generate correct code without some coaching. I am more impressed with an ability to understand the results, see where those hadn’t matched my intent and correct the error.

I tried a similar experiment with ChatGPT 3.5. It gave correct code, but the output it presented from the execution didn’t match a run of that code with a real Python interpreter. By the time I realized that the problem was correct code/incorrect output, the chat had devolved into confused statements trying to explain what happened.

I have been trusting that, if a query develops Python code, there would be an actual Python engine to execute it. The situation is more complicated than this. I’m not sure how to probe version 4 to see whether its Python is running correctly.

The hallucinations of an LLM can be more subtle than I anticipated. Their successes can be subtle as well.

49 Issues and ChatGPT

The New Yorker publishes every Monday. However, they have 4 issues each year that are double issues, each covering 2 weeks.

The first issue for 2024 is a Jan. 1/Jan. 8. I wondered whether the publisher’s decision was 48 issues a year or 4 double issues each year. I thought it would be interesting to know which years they have a choice.

In other words, I wondered what years in the 21st century have 53 Mondays in instead of the regular 52. When I asked ChatGPT 4 for the answer, I was surprised at how it found its answer.

Rather than having specific date training that could be consulted, ChatGPT wrote some Python code. It then ran the code giving the answer of 2001, 2007, 2012, 2018, 2024, 2029, 2035, 2040, 2046, 2052, 2057, 2063, 2068, 2074, 2080, 2085, 2091, 2096. If you look at the list, the years are spaced out every 5 or 6 years.

The code was:

from datetime import datetime, timedelta

# Function to check if a year has 53 Mondays
def has_53_mondays(year):
    # January 1 of the year
    jan_1 = datetime(year, 1, 1)
    # Count the number of Mondays
    monday_count = 0
    for i in range(365 + (jan_1.year % 4 == 0 and jan_1.year % 100 != 0 or jan_1.year % 400 == 0)):
        if (jan_1 + timedelta(days=i)).weekday() == 0:
            monday_count += 1
    return monday_count == 53

# List of years in the 21st century with 53 Mondays
years_with_53_mondays = [year for year in range(2001, 2101) if has_53_mondays(year)]
years_with_53_mondays

It’s understandable with a little work and doesn’t make any sophisticated assumptions. To get the answer, it visits every day so that it takes about 36525 steps to get the 21st century result. (With a deeper understanding of the calendar, it wouldn’t need the inner loop). This code offered a solution that is verifiable. (I just did a little research and Python’s calendar library doesn’t account for 1752.)

I only checked a few of its answers but the code looks robust.

I was curious what else it could do. I asked it for the first prime power greater than 1000000. The tool wrote some code and gave what looked like a reasonable answer.

# To find the first prime power greater than 1,000,000, we start by finding prime numbers and then checking their powers
# until we find a value greater than 1,000,000.

from sympy import primerange, isprime

def first_prime_power_above(limit):
    for prime in primerange(2, limit):
        power = 2  # Start from the square of the prime
        while True:
            prime_power = prime ** power
            if prime_power > limit:
                return prime_power
            power += 1

# Setting a limit for the search range. The exact limit is a guess; we need a range large enough to find our number.
search_limit = 10000
first_prime_power_above(1000000)

The code has a problem because it will only return a power of two. It only exits the inner loop with “return” and thus only analyzes powers of 2.

Armed with that understanding, I saw that it would fail on 150, so when I asked, it gave me 256. I knew 169 was the right answer. When I pointed out that it was using a greedy algorithm that only returns a power of two. It generated new code, below. It uses Python sets to calculate the result.

# Revisiting the approach to correctly find the first prime power greater than 150, considering all primes and their powers.

def first_correct_prime_power_above_v2(limit):
    prime_powers = set()
    for prime in primerange(2, limit):
        power = 2
        while True:
            prime_power = prime ** power
            if prime_power > limit:
                prime_powers.add(prime_power)
                break
            power += 1
    return min(prime_powers)

first_correct_prime_power_above_v2(150)

I was surprised that ChatGPT would generate code to make its analysis in both cases. The team implementing the engine saw that Python works as a lingua franca to implement algorithms. Simple Python is popular and has clean syntax.

My experiment demoed a feature that I didn’t expect. I can validate the code. ChatGPT lets me look under the hood while the textual responses won’t allow. It also shows that even unambiguous mathematical questions can yield incorrect results.

Bard was not useful. On the date question, it gave a list of incorrect answers. Rather than having a verifiable algorithm, it hallucinates a rule to explain its answer. Often its verbal algorithm isn’t correct and may not even make sense. Nor does the answer even follow the heuristic. I pointed out one of the mistakes in its year list and it just came up with a different confused rule it didn’t follow and got an even worse answer. I’ve noticed that Bard is inflexible and will double down on wrong answers.

For the prime power, Bard gave nonsensical steps to explain what it calulated. And then didn’t use that procedure to find the answer.


The first prime power greater than 150 is 169.

Here's the reasoning:

1. Start with the closest prime number after 150, which is 151.

2. Check if 151 is a prime power: 151 is only divisible by 1 and itself, making it a prime number, but not a prime power (prime powers must be raised to an exponent greater than 1).

3. Move on to the next prime number, which is 157. Again, 157 is only divisible by 1 and itself, confirming it's prime.

4. However, 157 raised to any positive exponent will still be an odd number, so it can't be the first prime power greater than 150.

5. Continue this process with the next prime numbers: 163 and 167.

6. 163 is not a prime power (163 = 7 * 23).

7. Finally, 167 is both a prime number and a prime power: 167 = 7^2, fulfilling both criteria.

Therefore, 169 (7^2) is the first prime power greater than 150. 

Bard offered two alternate responses, but they are just as jumbled and confused as this one. Step 4 is irrelevant. Step 6 is worse because 163 is prime. I don’t know why it has 167 and 169 confused nor why it uses seven squared.

Rather than lean on the English language to answer a mathematical question, ChatGPT uses an expertise in programming to answer math adjacent questions. This experiment demonstrates that it has basic algorithmic reasoning capabilities but is not consistently correct.

Going back to the original question, when I looked at a blow-in ad card, it said there are 4 double issues, so this year, I expect 49 issues with 4 of those being double issues.

An Acrobat touchup

A pushpin

I noticed that when you are printing with Adobe Acrobat on Windows, there has been a minor UI error for a long time. It’s been there since at least Acrobat Reader X in 2011. Now it’s fixed in recent versions such as 2023.008.20421 or later.

When you print a document, you can select a subset of the document’s pages. (For example “5-10”) Once one makes the selection, Acrobat updates a slider below the preview to only show the selected pages. Simultaneously, it replaces the text beneath the slider to show the new range of pages available.

Earlier versions would overprint the descriptive text until the preview changed.

The steps to reproduce the error:

  • Open a multipage document and start the print dialog.
  • Select the Pages radio button option in the “pages to print” area.
  • Type a subset of the document’s pages in the edit area.
  • Press tab

Formerly, one might get this overtype (For example, from a 2015 version of Acrobat):

A clip from a dialog with illegible text

(Once the user moved the slider, the text would be corrected.)

Now you get the information cleanly right away:

A clip from a dialog with the text 'Page 1 of 6 (5)

The longevity of this tiny bug is a testament to the advantage of getting software right in the beginning. Something subtle might not be noticed for a long time. It takes more verbiage to describe an error while finding it in early testing could prevent it from getting old.

GitHub Copilot Honor Pledge

I’ve been using GitHub Copilot recently with Microsoft Visual Studio while programming in C++. GitHub Copilot is an AI based software development assistant.

The initial observation is that it can be helpful. However, I found that it can violate the privacy of users whose code has been ingested by the language model.

In some big successes, it used comments as prompts to generate Win32 code. Once, the generated code revealed the exact function I needed. I didn’t know what to search to find the API that identifies the edge the taskbar is anchored. The code wasn’t exactly what I needed, but the results showed me where to look in Microsoft’s Win32 documentation.

When I was exploring a neural network project, it filled in a long list of varied neural network classes. As I customized my code, later code replicated the altered class outline. When it generates correct code after you set up the types and variable names, it can be fun.

My first critique is that it is sluggish. The time between when I am presented with a suggestion and Visual Studio registering my acceptance of it can be slow. Occasionally it can be insistent on making a change when I want to choose other code. Perhaps my PC needs an upgrade?

It is able to write descriptive comments about code. However, I have found that the description can be incorrect. It can write clear English, but what it says might not reflect an accurate understanding of the code. It can comment on the purpose of code based on insufficient evidence and can misinterpret a declaration and synthesize incorrect text. This requires a lot of vigilance. For example, I noticed, days later, that some of the text was superficially correct, but when I read it carefully, I saw that it was wrong. When Copilot is generating text, even if it isn’t correct, it fits the context. That makes it easier for errors to slip through.

A concern with Copilot is that it uses open-source content without honoring that code’s specific licenses. GitHub provides a website GitHub Copilot Trust Center with documentation of some of their policies. There’s a lot there to analyze. GitHub Copilot, is like ChatGPT and some of the image generating applications: they are using copyright law in ways that haven’t been clarified by legislation and litigation.

Once, as I was filling in the top of a file, Copilot dreamed up some unfamiliar code. When I went looking for it on GitHub, I couldn’t find the specific code but identified that it was derived from a project RED4ext that was unrelated to my application.

It was interesting that Bing and DuckDuckGo both give lengthy results when I search with the qualifier site:github.com. In contrast, Google gave minimal results for such a search; apparently it doesn’t index project text.

The “Github Copilot and Copyright” section of the Trust Center claims that their option “GitHub Copilot can allow or block suggestions matching public code” can prevent Copilot from using segments of about 150 characters or more from GitHub. However, usually, Copilot generates code a line at a time which will almost always be less than 150 characters. That claim seems to be a very weak promise.

I was starting a file and noticed a name in the header comments. I was intrigued and started auto-generating title comments for files with minimal code.

My first test alarmed me with this fully ironic text. The same pledge appeared several times in my explorations. The samples had different usernames.

// Honor Pledge:    
//     
// I pledge that I have neither given nor
// received any help on this assignment.
//  
// <<username elided>>

In a file that I had fully written, the header comment started with

// Created by: <<username elided>>
//  Created on: 7/10/2020 10:00:00 PM

Continuing my exploration of what it might generate, I got the full name of a user!

// Created by J*** K*** on 11/2/16.

These privacy violations make me interested in investigating GitHub Copilot’s promotional claims in more depth. As I complete more research, I should re-evaluate my use of the service.

GPT + Microsoft, Bard/Google and Beyond

The public conversation about AI tools is stuck focusing on ChatGPT, ignoring older uses of AI and other tools. Although it’s easy to use and flashy, but there is more to AI than a chat engine.

Microsoft has a couple of ways of interacting with a GPT derived chat tool. In the title bar of their Edge browser, there’s a prominent ‘b’ logo that opens a side panel to start a conversation. One enhancement to ChatGPT is that the Edge browser will give links referring to information sources. A conversation mode is also directly integrated with bing.com search results.

Google has an experimental chat service, Bard, at https://bard.google.com. My limited experience with Bard has been unsatisfactory so I have only used it a few times.

I have a specific use case with the interactive engines: helping me with programming language syntax and techniques. I’m not using it for the high-powered manner app-generating miracles that I’ve seen described in the media. Instead, I’m using it to supplement conventional help resources. As I learn more, I can ask more complex questions and develop increasingly useful skills.

Bard answers my questions in a very stilted manner. When I ask a programming question, the code it generates can be stand-alone. The code includes fluff such as verbiage allowing me to copy and paste the suggested code and directly execute it. That is frustrating because my goal is to learn how a feature works, not generate sample code. Bard is evolving, but what I’ve seen so far isn’t compelling me to use it. It prefers to give a specific, narrowly focused answer rather than explain a concept. I doesn’t know the context of my questions and give an example with minimal insight.

Bing’s search tool is much more useful to me. It remembers the context of my current questions. I don’t have to tell it “Python” or “JavaScript” every time I’m asking a new question. It presents example code that is relatively terse and succinct, helping me not get bogged down by unrelated details. I don’t expect the code to be stand-alone because I’m not looking for it to write code for me.

However, sometimes, a web search is more effective than using chat features. There are a few specialized sites such as https://stackoverflow.com that can answer questions. A search on Google, DuckDuckGo or Bing can go off base and include unneeded results, especially when the correct technical term has other generic uses. In one pleasing interaction, Bing’s top-line short answer was unrelated, but when I opened the chat, the chat answered the real software issue I was trying to understand.

It seems that ChatGPT is “sucking all of the Oxygen out of the room.” All of the news or blog commentaries talk about its threats and promise. They forget that AI has more uses beyond general-purpose conversational tools.

I have been using less prominent and limited AI for quite a while. Edge and Microsoft 365 (Office) both have been giving me suggestions as auto-complete so that I can accept with a tab press. It is not flashy, but it can save time. The keyboard interface to my iPhone’s messaging app also tries to predict what I intended. They are using Artificial Intelligence algorithms for that service. It is helpful.

My realization is that that AI is not a new tool. Amazon and others use it to identify potential sales based on its analysis of customer search and sales history. This Big Data application of artificial intelligence is old enough that it’s invisible now. It’s just called “the algorithm” and it is so ubiquitous that it’s often mundane.

If you don’t use the Edge browser and Bing search engine, you’re not going to see these additional ways of using the GPT Engine. I find them very productive. AI has a public face in ChatGPT, but there are other ways AI technology is common.

Quaternions are easier to understand as 5-dimensional

For a computer graphics developer, the 4-dimensional quaternion seems complicated, but by re-representing it as a 5-dimensional quantity, they can be easier to design with.

The idea doesn’t mean that quaternion algebra is altered, but a representation with 5 coordinates is simpler. It makes designing rotations easier to calculate.

A canonical quaternion is \(w+x\mathbf{i}+y\mathbf{j}+z\mathbf{k}\). or, as a vector \((w, x, y, z)\). The 5-dimensional representation is a vector \((m, \theta, a, b, c)\).

$$\sqrt{m}\left(\cos\frac{\theta}{2}+\sin{\frac{\theta}{2}}\left(a\mathbf{i}+b\mathbf{j}+c\mathbf{k}\right)\right) \text{ where } \|(a, b, c)\| = 1$$

Conjugating by this value corresponds to a rotation by \(\theta\) around the axis \((a, b, c)\) and scaling by a factor of \(m\). Although this is messier, it offers a much simpler conceptual framework.

The reason that this isn’t actually changing the dimensionality of a quaternion is because once you choose \(a\) and \(b\), then \(c\) only has two possible values. This drops the dimensionality back to its natural 4 again. The \(m\) usually drops out as well because if it is \(1\), there is no scaling in the rotation, the most common situation in computer graphics.

I’m loathe to use subscripts of \(\ _x\), \(\ _y\) and \(\ _z\) on the variables because they make the algebra indecipherable. I also would emphasize that understanding the meaning of the multiplication of two quaternions is irrelevant for computer graphics.

When rotating, one should only look at the rotation specification (conjugation by the quaternion), the point being rotated and the rotated point that is the result of the conjugation. I suggest not being intimidated trying to visualize 4-dimensional quaternion multiplication because the intermediate 4-tuples can be hidden behind a “rotate()” function and considered out-of-sight, out-of-mind.

It’s like organic chemistry. The result depends on the inputs and the process being applied. Once the process has been designed, the technician making the product does not need to know the details of the intermediate chemicals. Knowing basic safety rules can be enough.

In this form, if you want to rotate a graphic by an angle \(\theta\) around the vector \((p, q, r)\), you can start with the 5-parameter version describe above. Then you can multiply it out to get a normal quaternion:

$$\sqrt{m}cos\frac{\theta}{2} + a \left(\sqrt{m}\sin\frac{\theta}{2}\right)\mathbf{i} + b \left(\sqrt{m}\sin\frac{\theta}{2}\right)\mathbf{j}+c \left(\sqrt{m}\sin\frac{\theta}{2}\right)\mathbf{k}$$

You can then conjugate points by this constructed quaternion to rotate around an axis by the desired angle. The \(\sqrt{\ }\) is used because after the algebra calculating the rotation, it will result in a scaling by \(m\). \(\frac{\theta}{2}\) is used because the final rotation covers twice the angle used as the argument to the sine and cosine functions.

Since the value of \(m\) is going to be \(1\), you’re really finding 4 parameters. Now those 4 are a lot more meaningful than picking the values for a conventional quaternion without this extra step. The safety rule is that the rotation vector must be normalized to be a unit vector where \(\left(a^2+b^2+c^2\right) = 1\)

The derivation showing that any quaternion can fit into this representation starts with an arbitrary quaternion.

$$w+x\mathbf{i}+y\mathbf{j}+z\mathbf{k}$$

First, normalize the vector part so that it is becomes a unit vector by factoring out the magnitude of the vector.

$$w+\left(\sqrt{{x^2+y^2+z^2}}\right)\left(\frac{x}{\sqrt{x^2+y^2+z^2}}\mathbf{i}+\frac{y}{\sqrt{x^2+y^2+z^2}}\mathbf{j}+\frac{z}{\sqrt{x^2+y^2+z^2}}\mathbf{k}\right)$$

Note that the coefficients of the \(\mathbf{i}\), \(\mathbf{j}\) and \(\mathbf{k}\) form a unit vector, so I will write them as the desired \((a, b, c)\), simplifying the algebra a lot.

You can see that they are unit magnitude by squaring each segment and adding them:

$$\frac{x^2}{x^2+y^2+z^2}+\frac{y^2}{x^2+y^2+z^2}+\frac{z^2}{x^2+y^2+z^2}=\left(\frac{x^2+y^2+z^2}{x^2+y^2+z^2}\right)=1$$

Next, I simplify by factoring out the magnitude of the quaternion:

$$\left(\sqrt{w^2+x^2+y^2+z^2}\right)\left(\frac{w}{\sqrt{w^2+x^2+y^2+z^2}}+\frac{\sqrt{x^2+y^2+z^2}}{\sqrt{w^2+x^2+y^2+z^2}}\left(a\mathbf{i}+b\mathbf{j}+c\mathbf{k}\right)\right)$$

The next thing to observe is that the square of the coefficients of the vector and the scalar term, sum to \(1\) so that they are equivalent to the sine and cosine of some angle because of the identity \(\left(\sin{\phi}\right)^2+\left(\cos{\phi}\right)^2=1\). If two real number’s squares add to \(1\), there is some angle that fulfills the identity. Showing this uses the following derivation.

$$\left(\frac{w^2}{w^2+x^2+y^2+z^2}\right)+\left(\frac{x^2+y^2+z^2}{w^2+x^2+y^2+z^2}\right)=\left(\frac{w^2+x^2+y^2+z^2}{w^2+x^2+y^2+z^2}\right)=1$$

In summary, by representing a rotation as a 5-tuple, it’s easier to understand what is happening when you are doing rotations. Especially, since one of the parameters will normally be one, the four remaining parts of the quaternion have specific purposes that can be easily understood. For the purpose of rotation, quaternions are actually pretty simple.


Footnote: You can find warnings that one needs to be careful when the quaternion becomes “degenerate” because the vector part of the number is zero. This isn’t a problem with this description of applying quaternion rotations.

Normally, when you’re doing a rotation, you have an axis in mind for the rotation. This means that the vector part of the computed quaternion only becomes zero when \(\sin\frac{\theta}{2}\) is zero. That corresponds a rotation of zero. In such cases the vector part doesn’t matter, so a degenerate rotation just becomes a rotation around the same axis, but with \(\theta=0\)

Rather than turning a rotation by 0 into a special case, quaternions continue to eliminate special cases. Representing them as a 5-tuple makes the safety rules much easier to remember.

A Simple Hue Transformation

A while ago I needed a way to calculate colors. I wanted an easy way to get bright colors. I wanted it to be simple, taking a single hue parameter and mapping it into an RGB color.

The solution I found is to choose the red, blue and green channel values as overlapping trapezoids. The color is supposed to be cyclic, so I could used the fmod function in C++ or the % remainder operator in JavaScript. The result would transform any floating point argument into the base domain of\(\lbrack 0, 1)\).

Color map from zero to 1

The algorithm picks the values according to the above graph. As the hue argument moves from zero to 1, each color has a section where it is at its maximum value, part where it is zero. Between the two extremes is a linear transition. A zero value maps to zero in the corresponding RGB component while a 1 gets mapped to 255.

I’ve tried different curves for the transitions and the linear slope seemed to be the best compromise to make bright colors.

What I created is diagrammed here

This shows the colors broken into the individual red, green and blue channels.

By changing the shape of the basis curves, different results can be obtained. This trapezoidal function was chosen in the end because it gives really bright colors and is simple to implement. (Also, I have a long-ago program that already uses this design and if I want to replicate it online, I need the same algorithm.)

The following code in JavaScript calculates the function.


var color = (function() {
    const coords = [ 0, 1, 1, 1, 0, 0, 0];
    const factor = 6;
    const red = 0;
    const green = 2/3;
    const blue = 1/3;

    function fromHue( hue ) {
        hue = ((hue % 1) + 1) % 1;
        const index = Math.floor( hue * factor );
        const fraction = hue * factor - index;

        return coords[index + 1] * fraction + 
                        coords[index] * (1 - fraction);
    }

    return function( hue ) 
    { 
        let r = Math.round( 255 * fromHue( hue + red ) );
        let g = Math.round( 255 * fromHue( hue + green ) );
        let b = Math.round( 255 * fromHue( hue + blue ) );
        if( r !== r || g !== g || b !== b ) { 
             r = g = b = 0; 
        }

        return `rgb(${r},${g},${b})`;
    }
})( );

The coords array specifies the function’s value at each of the evenly spaced corners. The seventh value matches the first so that the curve is cyclic. Each hue that is not at a vertex will be a linear interpolation of the two quantities to the left and right. You can verify the values in the coords array by reading the red graph at the marked points on the hue axis. The coords table adds flexibility because different coords array and adjusting “factor” to match it can create other spectra.

Each color is out of phase by 1/3, Thus adding 1/3 and 2/3 to the hue will shift the graph so that the same trapezoidal curve can be used for each of the red, green and blue channels. Blue is 1/3 rather than green because if you shift the blue graph to the right by 1/3, it lines up with the red graph while green must shift right by 2/3 to match.

Inside the fromHue( ) function, the “((hue % 1) + 1) % 1” expression looks peculiar. The % operator returns the remainder of a division. For positive hue, the remainder of dividing by 1 is the fractional part. If hue is negative, the remainder will negative, between -1 and 0. Adding 1 and then calculating the remainder of that will map the entire domain for each color channel to the range [0, 1) without needing an “if.” By mapping hues into that range of 0 to 1, it is as if the above diagram repeats indefinitely to the left and right along the hue axis.

The Math.floor() operation takes a hue times six and converts it into an integer index for the coords array. By multiplying by 6, the corner points have integer indices and the fractional part left by subtracting index can be used to do the interpolation.

In the return statement of fromHue( ), “index + 1” accesses the value of the function to the right of the calculated fraction. Since the points of the coords array are 1 apart, the slope is \(coords\lbrack index + 1\rbrack – coords\lbrack index\rbrack\)

In point slope form, the value is

$$coords\lbrack index\rbrack + (coords\lbrack index +1\rbrack – coords\lbrack index \rbrack ) * fraction$$

Combining the two references to \(coords\lbrack index \rbrack\) results in the expression above.

After calling fromHue(), the calls to Math.round( ) take each component and map it to an integer from 0 to 255. The text that it returns is appropriate for using for the color in styles and graphic contexts.

The final “if” statement detects when the results are NaN and replaces that with black. NaN is the only value that is not equal to itself. Whenever the argument hue is not a number, fromHue( ) will return NaN.

For example, if hue were 1.6, for Red, the first expression would replace hue with 0.6. Then, index would be Math.floor( 6 * 0.6 ); or floor( 3.6) or 3. Fraction would be 0.6. Coords[ 3 ] is 1 and coords[4] is 0. The result then is 0 * 0.6 + 1 * (1 – 0.6) which evaluates to 0.4. Then, red would be set to Math.round(255 * 0.4) which is 102. For Green, fromHue would be given about 2.267 which changes to 0.267 when you take the remainder. Index would be Math.floor( 6 * 0.267 ) or floor( 1.6 ) or 1. Coords[1] and coords[2] are both 1 so the return statement would return 1. This would then become 255. For Blue, fromHue would be given about 1.933 which would become 0.933. This leads to an index of 5 and fraction of 0.6. Coords[ 5 ] and coords[ 6] are both 0 so blue would be 0. Since none of these are NaN, the result would be the string “rgb(102,255,0)”

Because the corners in the trapezoidal shapes are positioned at constant intervals, the algebra is simplified. Without a repeating shape or with unevenly spaced key points, the algorithm would need an inelegant sequence of ifs to calculate the curves.

Animation as a torture test

I’ve been working on an animation/simulation project. I’ve found that animation can be a torture test of rendering tools. In these cases, I’ve been using HTML5 2d Canvases. Quirks that don’t show up in a review of still snapshots of a scene can become visible in an animation.

One of the animations would draw circles that were growing to infinite size and then shrinking on the opposite side. When I was exploring the animation, I would find that the circles would twitch when they got really big. They were supposed to be tangent circles at all times but that was not what I was finding.

I thought my math was wrong, perhaps with round off problems with the large numbers involved or my confusion on the complex number arithmetic.

You can see in the last frame a still of the problem being evident. The lower blue line should be tangent to the smaller circles. Eventually, I tested the animation on a different browser with a different rendering engine and found that it didn’t have the problem. My math was correct after all.

I was able to narrow down the problem with this graphic.

These circles were designed to be tangent but as the radius increased, they were getting away from the expected position. It appears that it’s a problem with an optimization in the arc routine. I was able to hide the problem in my animation by replacing the large radius arcs with interpolated line segments.

Another subtle problem is demoed in this next video. The top ‘x’s wiggle as they move. The lower one from a different rendering engine has the letters move smoothly.

I think the problem here is that the renderers do different antialiasing when a character’s coordinates are not integers.

This one was most interesting because when I tried to reproduce the problem with other angles and velocities for the motion, the effect was invisible. I managed to find the problem by accidently testing with the perfect combination of settings.

Both of these are issues that are minor and I intentionally didn’t name the browsers involved. It’s not important to the main point, that animation can demo subtle problems that aren’t visible when generating still images.

A New Home

An essential WordPress feature that I use on this blog is the ability to format mathematical expressions. At the old home, I was using the plugin Katex. I just noticed that it was only working in Firefox. Unfortunately, I’m not sure how long it had been broken.

WordPress.com’s customer service wasn’t helpful, although they did point out that Katex hadn’t been updated for over a year. I found that the MathJax-LaTeX plugin was being actively maintained. On the old host’s service MathJax was horribly broken. It didn’t support colors, which I used in the previous post, An Expanded Multiplication Table. It only supported inline expressions and the background of inline expressions was white which didn’t match the blog’s background.

When I combined those failures with the high cost of the required upgrade at wordpress.com, $25/month, I searched for a new home. I’m glad that I found the problem before the blog renewed in a few weeks at $300 for another year!

When I searched for reviews, this host, IONOS was ranked highly. I already have been using IONOS for hosting my other websites, so I moved the blog there too. It appears that I didn’t lose much in the transition. It took me an afternoon to get the content transferred and an hour or two to get the site reconfigured. Most of that time was spent fixing the color references.

It was easy to find help for MathJax-LaTeX on the Mathematics StackExchange. For example, MathJax basic tutorial and quick reference and that is just scratching the surface of resources.

I wonder why I didn’t make this transition sooner. Spending $12 instead of $300 seems like enough incentive to change, but inertia is a powerful demotivator. My other WordPress blogs can follow.

{{Update: It turns out that the other blogs are stuck because I can’t transfer the media library. However, the financial benefit of transferring them only applies to the first year so the incentive isn’t as strong. It appears that transferring the media library is a difficult problem because its configuration is set by the managed hosting provider. Different hosts might use incompatible file naming schemes. In addition, the two cheaper blogs, blog.wwayneb.com and blog.sesquibits.com only have a limited export tool.}}

An Expanded Multiplication Table

In the Dune stories, mentats are trained to do complex calculations without any mechanical aids. For normal people, instantly knowing the multiplication up to 10 is a useful skill. Extending the multiplication table to twenty seems to require the skills of a mathematical savant.

However, calculating products of up to 20 actually only requires the single digit multiplication table and the ability to add small numbers mentally. My goal is to be able to do these calculations without paper. How?

If one of the numbers is single digit and the other is between 10 and 20, the calculation works like this:

A number between 10 and twenty can be written as \(10 + a\) or \(1a\). For the results you have one single digit multiplication an addition of a number times ten.

$$1a * b = (a*b) + (10 * b)$$

Since \(10 * b\) is just \(b\) shifted left by one, you can get the result by identifying \(a*b\) and then adding \(b\) to the second digit.

$$1\color{blue}{5} * \color{red}{7} = (\color{magenta}{35} + \color{red}{7}0) = 105$$

The mental steps are “calculate \(\color{blue}{5} * \color{red}{7}\)” and then “add \(\color{red}{7}\) to the second digit.”

For the product of a single digit times and a number under twenty, you always use the normal multiplication table and single digit addition.

If both numbers are between 11 and 19, the calculation works out this way.

$$a * 1b = (10 +a) * (10 + b) = a * b + 10 * (a + b) + 100$$

To start, multiply the singles place values. For example \(1\color{blue}{5} * 1\color{red}{7}\) starts as \(\color{magenta}{35}\).

Then add \(\color{blue}{5} +\color{red}{7}\), giving \(\color{orange}{12}\).

Add that in the 10s place \(\color{magenta}{35} + 10 * \color{orange}{12}\) giving \(\color{brown}{155}\) and finally, increment the hundreds digit, giving \(\color{green}{255}\)

This involves multiplying single digit numbers, adding a pair of single digits, adding a digit and a number less than twenty and incrementing a digit. It works out to needing having six or so digits in mind at once which can be less than one’s estimated working memory of 7 digits.

If one of the numbers is 10, add a zero to the right of the other value. For twenty, double the other number and add a zero. Now there’s a simple way to multiply two numbers where both are 20 or less.

With a little practice, I can do this in my head. More importantly, I’m confident of my result so that I’m getting so that I don’t need to double check on a calculator.