\n",
"\n",
"# Stubborn Number Endings (e.g. 5² = 25)\n",
"\n",
"[Francis Su](https://www.francissu.com/)'s fine book *[Mathematics for Human Flourishing](https://www.francissu.com/flourishing)* mentions the fact that numbers that end in \"5\" have a square that also ends in \"5\". \n",
"\n",
"For example, 5² = 25, 15² = 225, and 25² = 625. \n",
"\n",
"This leads to some questions:\n",
"\n",
"- Is there an easy way to calculate the square of a number ending in \"5\" without using a calculator?\n",
"- What should we call this property of \"square has same ending\"?\n",
"- Are there other digits besides 5 that have this property?\n",
"- Are there multi-digit endings that have this property?\n",
"- Can we prove it, not just show some examples?\n",
"\n",
"\n",
"## Is there an easy way to calculate the square of a number ending in \"5\"?\n",
"\n",
"Let's make a table of {number: square} pairs and look for a pattern:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "fd3c4f25-fb63-43e5-925c-e4b7b8f134ec",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{15: 225,\n",
" 25: 625,\n",
" 35: 1225,\n",
" 45: 2025,\n",
" 55: 3025,\n",
" 65: 4225,\n",
" 75: 5625,\n",
" 85: 7225,\n",
" 95: 9025}"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{i: i ** 2 for i in range(15, 100, 10)}"
]
},
{
"cell_type": "markdown",
"id": "44d2cf09-b397-46d7-ab4f-e068e571895d",
"metadata": {},
"source": [
"I see a pattern here: for example 95 squared is 9025, which you get by taking the \"9\", multiplying it by one more than itself to get 9⋅10 = 90, then squaring 5 to get 25, then putting the \"90\" next to the \"25\" to get \"9025\".\n",
"\n",
"Does the trick also work for numbers with more digits? Let's investigate:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "7681cb5f-bf5a-4317-b842-e0be21745d90",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{105: 11025,\n",
" 115: 13225,\n",
" 125: 15625,\n",
" 135: 18225,\n",
" 145: 21025,\n",
" 155: 24025,\n",
" 165: 27225,\n",
" 175: 30625,\n",
" 185: 34225,\n",
" 195: 38025,\n",
" 205: 42025,\n",
" 215: 46225,\n",
" 225: 50625,\n",
" 235: 55225,\n",
" 245: 60025}"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{i: i ** 2 for i in range(105, 250, 10)}"
]
},
{
"cell_type": "markdown",
"id": "89b1e783-0546-4305-989c-b3b83781cf92",
"metadata": {},
"source": [
"Yes, it looks like the trick still works: 105 squared is 10⋅11 = \"110\", followed by \"25,\" to make \"11025\". And 245 squared is 24⋅25 = \"600\", followed by \"25\", to make \"60025\". \n",
"\n",
"\n",
"## What should we call this property?\n",
"\n",
"Let's define an **ending** as the rightmost-digits of an integer in decimal notation. \n",
"\n",
"Then we can say that an ending is **stubborn** if every integer with that ending has the same ending when squared.\n",
"\n",
"## Are there other digits besides 5 that are stubborn?\n",
"\n",
"We could work this out in our heads, or with paper and pencil, or we can compute it:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "ddcdafa8-bfa6-4707-951c-6683701e06c2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0, 1, 5, 6}"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{e for e in range(10) if str(e ** 2).endswith(str(e))}"
]
},
{
"cell_type": "markdown",
"id": "f4cb87cf-2fa8-4d2e-bc24-94c378930792",
"metadata": {},
"source": [
"These are the four digits whose square ends in the same digit.\n",
"\n",
"## Can we prove stubborness?\n",
"\n",
"We have seen that 0² ends in 0, 1² ends in 1, 5² ends in 5, and 6² ends in 6. And we have checked some numbers with those endings, for example, 245² ends in 5. But can we **prove** that **every** integer ending in 0, 1, 5, or 6 has a square that ends in the same digit?\n",
"\n",
"Some notation: I'll use quote marks, as in \"*se*\" to mean the string of starting digits \"*s*\" followed by the string of ending digits \"*e*\".\n",
"\n",
"With a little bit of algebra we can see that if *s* is any string of digits and *e* is a single ending digit, then:\n",
"\n",
"\"*se*\"² = (10⋅*s* + *e*)² = (10⋅*s*)² + 2⋅(10⋅*s*⋅*e*) + *e*² = 10 ⋅ (10⋅*s*² + 2⋅*s*⋅*e*) + *e*² = *e*² (mod 10)\n",
"\n",
"In other words, \"*se*\"² is 10 times some integer plus *e*², so \"*se*\"² ends in the digit *e* if and only if *e*² ends in *e*. \n",
"\n",
"We know that is true for 0, 1, 5, and 6, and for no other digits.\n",
"\n",
"## Are there multi-digit endings that are stubborn?\n",
"\n",
"The algebraic argument above can be extended to work with an ending string *e* that is *k* digits long:\n",
"\n",
"\"*se*\"² = (10*k*⋅*s* + *e*)² = (10*k*⋅*s*)² + 2⋅(10*k*⋅*s* ⋅ *e*) + *e*² = 10*k* ⋅ (10*k*⋅*s*² + 2⋅*s*⋅*e*) + *e*² = *e*² (mod 10)\n",
"\n",
"Again, \"*se*\"² ends in *e* if and only if *e*² ends in *e*. To test whether *e* is a stubborn ending (with all possible starting digit strings), all we have to do is square *e* and see if the result ends in *e*. \n",
"\n",
"There is one **complication**: we'd like to say that \"0\" and \"00\" are two distinct stubborn endings, but 0 and 00 are the same integer. To distinguish them, I will describe endings as strings, not integers. I will define the predicate function `stubborn(ending: str)`:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1419b122-257d-475b-90c6-e832c0aae8cf",
"metadata": {},
"outputs": [],
"source": [
"def stubborn(ending: str) -> bool:\n",
" \"\"\"Does every integer with this ending, when squared, also end with this ending?\"\"\"\n",
" start = \"1\" # Arbitrary choice of start; could be any non-zero digit string\n",
" square = str(int(start + ending) ** 2)\n",
" return square.endswith(ending)"
]
},
{
"cell_type": "markdown",
"id": "9ec3ae19-96cb-4bea-ae6f-075b3ade7d3b",
"metadata": {},
"source": [
"Now we can find all two-digit stubborn endings as follows:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "57e64e31-0674-4849-bf72-f6c6b437009a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['00', '01', '25', '76']"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"digits = '0123456789'\n",
"endings = [s + e for s in digits for e in digits]\n",
"\n",
"list(filter(stubborn, endings))"
]
},
{
"cell_type": "markdown",
"id": "61a2b8e2-2bdf-458f-adaf-ffde56125862",
"metadata": {},
"source": [
"We could easily continue on in this way, enumerating all three-, four-, or more-digit endings, and checking each one to see if it is stubborn. There are only a million possible six-digit endings; we could quickly check them all. But there are a quadrillion 15-digit endings, so it would take a *long* time to check all of those. And 100-digit endings? Forget about it. Instead we need to rely on an optimization:\n",
"- Any two-digit stubborn ending ('00', '01', '25', '76') has to end in a one-digit-stubborn ending ('0', '1', '5', '6').\n",
"- In general, any *d*-digit stubborn ending has to end in a (*d*-1)-digit stubborn ending.\n",
"- So, to find the stubborn endings of length 100, I don't need to generate and test all 10100 endings, I only need to consider the stubborn endings of length 99, prepend each of the ten digits to each of these endings to get a list of 100-digit endings, and then check each one for stubborness. \n",
"\n",
"Using this optimization we can efficiently compute all stubborn-endings of a given length *d* as follows (caching greatly improves efficiency):"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "abeba9cb-1357-4e2b-a661-8616d0d3f768",
"metadata": {},
"outputs": [],
"source": [
"from functools import cache\n",
"\n",
"@cache\n",
"def stubborn_endings(d: int) -> list[str]:\n",
" \"\"\"A list of all stubborn endings of length `d` digits.\"\"\"\n",
" if d == 0:\n",
" return [''] # The empty ending is the only stubborn ending of length 0.\n",
" else:\n",
" endings = [s + e for e in stubborn_endings(d - 1) for s in digits]\n",
" return list(filter(stubborn, endings))"
]
},
{
"cell_type": "markdown",
"id": "05c524e7-6fa8-4fdd-875d-ef5cc0453560",
"metadata": {},
"source": [
"For example:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "3943be37-ec8a-4ec0-b946-acb2f449dd72",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0: [''],\n",
" 1: ['0', '1', '5', '6'],\n",
" 2: ['00', '01', '25', '76'],\n",
" 3: ['000', '001', '625', '376'],\n",
" 4: ['0000', '0001', '0625', '9376'],\n",
" 5: ['00000', '00001', '90625', '09376'],\n",
" 6: ['000000', '000001', '890625', '109376'],\n",
" 7: ['0000000', '0000001', '2890625', '7109376'],\n",
" 8: ['00000000', '00000001', '12890625', '87109376'],\n",
" 9: ['000000000', '000000001', '212890625', '787109376']}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{d: stubborn_endings(d) for d in range(10)}"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "38c26d56-d916-481e-bc04-51af7a52b1e8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',\n",
" '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',\n",
" '3953007319108169802938509890062166509580863811000557423423230896109004106619977392256259918212890625',\n",
" '6046992680891830197061490109937833490419136188999442576576769103890995893380022607743740081787109376']"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"stubborn_endings(100)"
]
},
{
"cell_type": "markdown",
"id": "37a45341-8dac-4720-be7d-388fa47eec1a",
"metadata": {},
"source": [
"This leads to a few new questions.\n",
"\n",
"## Can each stubborn ending always be extended exactly one way?\n",
"\n",
"There are four stubborn endings of length 1: '0', '1', '5', and '6'. So far, every time we try, each of the four can be extended from length *d* to length *d*+1 in exactly one way. Will that always be the case? I leave it up to you to provide a proof, but I can show that it is true for every *d* up to 2000:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "654d53af-e58f-4ed2-874b-c4573f3a9a7a",
"metadata": {},
"outputs": [],
"source": [
"def last_digits(endings) -> list[str]: \n",
" \"\"\"A list of the last digits of these endings.\"\"\"\n",
" return [e[-1] for e in endings]\n",
" \n",
"for d in range(1, 2000):\n",
" assert last_digits(stubborn_endings(d)) == ['0', '1', '5', '6']"
]
},
{
"cell_type": "markdown",
"id": "537556a3-d570-4a50-93bf-9632b40f9b47",
"metadata": {},
"source": [
"## What digits are used to extend each ending?\n",
"\n",
"I don't have a theory of which digit is used to extend each stubborn ending, but I can count:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4dbd7ace-8b41-4ea7-b71f-8f7e318243e1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'0': Counter({'0': 1999}),\n",
" '1': Counter({'0': 1999}),\n",
" '5': Counter({'8': 214,\n",
" '2': 208,\n",
" '4': 206,\n",
" '0': 205,\n",
" '7': 205,\n",
" '9': 198,\n",
" '6': 197,\n",
" '1': 197,\n",
" '5': 196,\n",
" '3': 173}),\n",
" '6': Counter({'1': 214,\n",
" '7': 208,\n",
" '5': 206,\n",
" '9': 205,\n",
" '2': 205,\n",
" '0': 198,\n",
" '3': 197,\n",
" '8': 197,\n",
" '4': 196,\n",
" '6': 173})}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from collections import Counter\n",
"\n",
"{end: Counter(start) for (*start, end) in stubborn_endings(2000)}"
]
},
{
"cell_type": "markdown",
"id": "35e84df9-0b76-4d8d-aa13-2c4e73a86643",
"metadata": {},
"source": [
"This is saying that the stubborn endings \"0\" and \"1\" are always extended by prepending a \"0\", but for \"5\" and \"6\", it seems to be pretty random; each of the ten digits appears roughly 200 times."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python [conda env:base] *",
"language": "python",
"name": "conda-base-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}