Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

SpaceChem Bonders Explained

Last updated:

I just finished SpaceChem (a great, frustrating puzzle game!), and toward the end I looked up solutions to a few puzzles, but got frustrated that they didn't work for me. It always failed somehow at the "bonders", spots you can move around that will bond atoms together into a molecule. Eventually I got frustrated by an intermittent failure in a level, and looked up the details. Because it was hard to find the full behavior details of bonders, I thought I'd reproduce them here, to help spread the data.

Each bonder has a hidden "priority" number, which can't be determined except by experimentation. Each bonder is tested in priority order to see if it can affect a molecule bond. Once a bonder is chosen, it looks down and right only, checking for a second bonder in priority order as well. This continues through all the bonders.

So, when you hit a Bond+, SpaceChem first checks the 1 bonder: it looks down and right to see if the 2 bonder is there, then to see if the 3 bonder is there, etc. Once it's run through the list of bonders, it checks the 2 bonder: it looks down and right to see if the 1 bonder is there, then to see if the 3 bonder is there, etc. And this continues through all the bonders; at the end, every bonder link will have been checked.

The bonder priority is always the same when you start a level - if there are 4 bonders in a square, the order is:

1 2
4 3

If there are 8 bonders arranged vertically, the order is:

1 2
4 3
5 6
8 7

I'm not sure what the priority order is of the levels where there are only 2 bonders, or 8 bonders arranged horizontally.

Apparently, if you roll your mousewheel over a bonder it will change the priority, but nobody has any details on how this works.

Pandemic:Legacy Fantasy Reskin

Last updated:


(This first part will just cover the sorts of things you'd get from a normal Pandemic game, so it shouldn't be spoilery. The bad stuff comes a little bit later. I'll have one more warning before then.)

Pandemic:Legacy is an update/refresh of the classic 2008 cooperative boardgame Pandemic, which has the players controlling a team of scientists from the CDC as the world is rocked by four simultaneous plagues. Pandemic:Legacy adds "legacy" mechanics to the game, a newish game-design trait where the game is permanently changed across plays, with game components being added, removed, or defaced over time. This update adds wonderful flavor to the game, suggesting a world declining into chaos as the plagues strike again and again.

For some reason I often get the urge to reskin cool games into fantasy themes, and Pandemic was no exception. Legacy just makes me even more excited!

Basic Premise

Centuries ago, the Lich-lord Gaxkang threatened our beautiful kingdom with his demon hordes. He was too powerful to be permanently killed, but a party of brave adventurers were able to seal him away, and with him the demons he had summoned. Unfortunately, Gaxkang's shadow has fallen across our kingdom yet again. For some yet-unknown reason the seals on his prison are weakening, and demons are escaping to wreak havoc and unleash their master.

Your task, brave adventurers, is to seek out magical artifacts linked to ancient mystical sites in our kingdom, which have been scattered and lost in the centuries since they were last used, and with them channel enough power to restore the seals guarding Gaxkang's prison.

You all start at your Guildhall. Each turn, you can take up to four actions:

  • fight demons in your area (remove one cube)
  • move between areas
  • teleport between Guildhalls (you start with one)
  • use up an artifact to Direct Teleport from anywhere to the artifact's matching area (you can't Direct Teleport in or out of a area that is rioting or worse)
  • use up an artifact to True Teleport from the artifact's matching area to anywhere (you can't True Teleport in or out of a area that is rioting or worse)
  • found a new Guildhall by using up the artifact matching that area (normally tying a Guildhall into the local mana networks takes years of careful work; with the local artifact it can be done immediately)
  • if you and another party member are both in the area matching an artifact one of you has, give/take the artifact to/from the other person (artifacts are mystically bound to the person who found them; rebinding them to another soul can only be done in the area they're linked to)
  • if you have 5 artifacts of the appropriate type, and are in a Guildhall, restore the corresponding Seal

Then you draw two new artifacts from the Artifact Deck (possibly triggering a demon surge), and then turn over new cards from the Invasion Deck to see where to add more demons. Play then passes to the next character.

If the Invasion deck would add a 4th demon to an area, it instead triggers a Tear In The Veil - the demons have reached a critical mass, weakening the local weave of reality so much that demons can temporarily pour forth more freely. Add one demon of the corresponding type to all neighboring areas (possibly triggering more Veil Tears), that area goes up one panic level, and you increase your Tear Count by 1. If any characters are in the area, they gain a Scar.

  • If you get 8 Veil Tears in a single game, the prison is weakened enough to allow Gaxkang to escape, losing you the game.
  • If you need to place a demon on the board for any reason, but there are no cubes of that demon type left in the supply, enough of a demon army has broken through to tear open Gaxkang's prison, losing you the game.
  • If you attempt to draw from the Artifact Deck but there are no more artifacts to draw, enough time has passed that Gaxkang has broken from his prison himself, losing you the game.
  • If you can Restore all four Seals before either of the previous conditions occur, you've (temporarily) secured Gaxkang's prison, and won the game.


You start with five characters available:

  • Paladin (previously Medic): This holy warrior is superb at demon-slaying. When they fight demons, they kill all the demons of a given type from an area. After a Seal has been Restored, the weakened demons are automatically destroyed by the paladin simply being in the area, without the Paladin spending an action, and also prevented from spawning the Paladin's area.
  • Monk (previously Researcher): Monks train to resist all external influences, including the spiritual. Artifacts never bond to the Monk, allowing them to Trade their artifacts to another player without having to be in the matching area. This applies whenever the Monk gives an artifact to another player, or another player takes an artifact from the Monk. The Monk and the other player still have to be in the same location, as normal.
  • Rogue (previously Generalist): The Rogue has no special magical talents, instead focusing on flexibility and action. The Rogue can take 5 actions per turn, rather than 4, and can accept 4 character upgrades, rather than 2.
  • Wizard (previously Dispatcher): While this magic-user can blast demons as well as the next, their specialty is teleportation magic. As an action, the Wizard can move any other character's token as if it were their own, or teleport any character (including themselves) to the same location as another character. (If movement would require spending an artifact, such as Direct Teleporting to an area or entering a collapsing area, they must come from the Wizard's hand. If the location of the moving pawn matters, such as Full Teleporting out of a city, it's the location of the other player's pawn that matters.)
  • Cleric (previously Scientist): This battle-priest's faith and devotion allows them to draw on their gods' power to help in restoring seals. The Cleric only needs 4 artifacts to Restore a Seal, rather than 5.

Additional Miscellaneous Things

When a Seal has been Restored, the demons of its color are weakened. A character taking the Fight Demons action can choose to remove all demons of the Restored Seal's color, rather than just one. If there are several colors of demons, the character must still Fight Demons once for each Restored color to destroy those demons.

If the last demon of a Restored Seal's color is destroyed, that color of demon has been Eradicated. Eradicating demons is not required to win, but when areas of an Eradicated color are Invaded, no new demons are placed there. Destroying the last demon of an unrestored Seal has no special effect.

"Funded Events" are additional magic artifacts, not linked to an area, with special powers. They can be used at any time, even if it's not your turn. (These need to be reskinned, but I haven't taken the time to do so yet.)

Two Scars need reskinning:

  • Spiritually Scarred (previously Insomniac): Reduce your hand limit by 1.
  • Chaotic Aura (previously Fear Of Small Planes): Discard an artifact to teleport between Guildhalls.


Game End Upgrades

At the end of each game the players can choose up to two upgrades, to reflect their growing experience and help from the powers-that-be.

If a demon type was Eradicated in the game, you can learn from that experience:

  • Level 1, Familiarity: You understand the basics of restoring this seal, and no longer need the libraries and equipment of a Guildhall to do it - Restoring can be done anywhere if you have the correct artifacts.
  • Level 2, Efficiency: Restoring this Seal has become almost routine - you can Restore at any time without spending an action, even on other player's turns, if you have the correct artifacts.
  • Level 3, Mastery: You're so skilled at restoring this seal that you can do it with much less power than it would normally require - Restoring this Seal takes 1 less artifact than normal.
  • Level 4, Suppression: You've mastered the seal to such a degree that you can partially restore it without any artifacts, weakening the demons linked to it. When any character Fights Demons, they destroy all demons of this color from their area.

Character upgrades are mostly fine as they are. A few that need reskinning:

  • Inspiring Leader (previously Local Connections): Once per turn you can Fight Demons in a neighboring area.
  • Teleportation Expert (previously Pilot): Don't discard an artifact when doing a Direct Teleportation, just show it from your hand.
  • Seer (previously Forecaster): At the start of your turn, look at the top 2 Invasion cards.

Some later upgrades, just because I don't remember exactly when they showed up:

  • Elven Training (previously Veteran): You may take the Hidden Paths action. If you're human, you also count as an elf for effects that care about such things.
  • Attuned With Nature (previously Local Pressure): If you are in an Elven Grove, you may Strengthen The Veil in an adjacent area with the Strengthen action.
  • Elven Escort (previously Paramilitary Escort): Whenever you leave an area with a Ravage demon, destroy 1 Ravage demon from that area. Only Elves can use this upgrade.
  • Infiltrator (previously Chopper Pilot): Cross any Totem Ward without discarding an artifact.

Unfunded Events represent upgrades and tweaks you make to artifacts, increasing their functionality. I haven't reskinned these yet either.

Permanenting a Guildhall needs no reskinning.

January Changes

After your second Demon Surge, you find that one of the demon types is far stronger than the others. Calling themselves Endless Ravage, they've also done something strange to their Seal, preventing it from being Restored. Fighting the Ravage takes an additional action, and you can't Restore their Seal. Your goal is now to just Restore the other three Seals.

February Changes

The neighboring Elven kingdom has responded to our requests for help, and sent a hero of their own to aid you:

  • Elven Mystic (previously Quarantine Specialist): Once per turn, the Mystic can Strengthen The Veil anywhere on the board. If the veil is strengthened in the area she's in, nothing weakens it.

All characters can now spend an action to Strengthen The Veil in their area. If the veil is strengthened in an area, the next time demons would be placed there, instead the veil is weakened to normal, removing the Strengthened marker.

This was just in time, as the Ravage has gotten stronger, and can no longer be Fought at all.

Also you can now add Backstory to your characters:

  • "Friends with" (previously "Family members with"): The two of your work well together, and complement each other's efforts. If you start your turn in the same area as your friend, gain 1 extra action.
  • "Family members with" (previously "Coworkers with"): The souls of family members are very similar, and the artifact bond can be easily shifted between them, taking the artifact with it. You can Trade an artifact with a family member as long as at least one of you is in the artifact's area - the other character can be in a different area entirely. (The Monk can pass any artifact.)
  • "Fate-bonded with" (previously "Friends with"): The two of you have fates mystically intertwined for some reason, and this occasionally grants you flashes of insight of the near future when you are together. If you start your turn in the same area as your fate-bond, you can look at (but not rearrange) the top two cards of the Artifact deck.
  • "Rivals with" (unchanged): Whenever your rival discards an artifact for any reason, you can discard two artifacts to pick it up.

March Changes

More Elven assistance, in the form of the Elven Druid:

  • Elven Druid (previously Operations Expert): Using his deep connection to the land, the Druid can weave Guildhalls into the local mana network without using up an artifact. He can also establish Elven Groves, a new Elf-only structure that is not removed by unrest in the area. Finally, the druid can use up an artifact to teleport from any structure to anywhere on the board.

Elven characters can now use the Hidden Paths action, which lets them teleport between Elven Groves as an action, similar to the Guild Teleport.

Two new optional objectives as the demon hordes grow worse: Eradicate one type of demon, or establish Elven Groves in all six regions of the kingdom.

April Changes

Shit. Gets. Bad.

After your second Demon Surge, Eternal Ravage unleashes a new ability - it Taints any area it shows up in. Tainted areas only spawn Ravage demons, and strengthen them - any character starting their turn in an area with a Tainted Ravage demon takes a Scar. While this is initially limited to the Ravage's area of the kingdom, if a Veil Tear occurs and spreads Ravage demons to other areas, the Taint spreads as well.

May Changes

In response to the Taint, the Elves send a new ally to the cause:

  • Elven Barbarian (previously Colonel): As long as there is an Elven Grove in the region of the kingdom, the Barbarian can Fight the Ravage. The Barbarian can also pass through Totem Wards without spending artifacts. However, the Barbarian is terrible at Restoring Seals, and needs 2 additional artifacts to do so.

The elves also teach you how to build Totem Wards, which block all movement between any two adjacent areas. Veil Tears no longer spread demons along that route. However, you can't move across wards easily either - you must use up an artifact to move between areas with a Totem Ward between them.

The Ravage have altered their Seal in some terrible way, as well - whenever an artifact of their Seal is found, add a Ravage demon to that artifact's area.

New optional objective added: Have 7 Tainted areas Strengthened at once.

June Changes

You can now upgrade artifacts on the fly to help you with new abilities. Your own artificers have developed some upgrades you can apply in your Guildhalls (defensive spells, or things that aid in Restoring), while the Elves have developed their own that must be applied in Elven Groves (offensive spells, or spells letting them move around in our kingdom more easily).

The elves also send another hero to help the cause:

  • Elven Spiritblade (previously Soldier): The Spiritblade has magical stealth, and does not take a scar for starting his turn in an area with Ravage demons. He can also restore any artifact that has been upgraded, calling it back to his hand from the discard pile. However, he cannot Restore a Seal at all.

July Changes

A renowned demonologist, presumed dead, was spotted in a Tainted area. You must find her, as she might have insights in how to fight the Ravage!

Now you have a Search board, and must find the Demonologist before their trail goes cold. Searching requires you to use up an artifact of the same color as the area you're searching; using the matching artifact searches even better. Rumors place them in an area with a Guildhall, and searching is easier if the veil is Strengthened, as there's less demon threat. Every Demon Surge makes it less likely you'll find them, though.

If you do find them, they reveal that they've returned from retirement and have been studying the Ravage. They believe they can discover a weakness, but need to find their demon-hunter ally, who they were separated from in a rioting area.

August Changes

Now you're searching for the Demon-Hunter, in some Tainted rioting area. Humans in the area help the search.

You also develop a new action - Blood Sacrifice. Any character can voluntarily take a Scar as an action to skip the Invade step this turn.

If you find the Demon-Hunter, they tell you they can find the Ravage's weakness, but they need more resources. First they need someone to study the Ravage's behavior (which the Demonologist has done, if you found them). Second, it looks like the Ravage didn't develop their powers themselves - some group has been aiding them. The Demon-Hunter needs to discover which group has been enhancing the Ravage. Finally, the Demon-Hunter needs more information about the Taint and its properties.

Also, the elves offer up a dark ritual they know of. It drains one area of all its mana, permanently, in order to completely cut off another area from the spiritual realm. This kills all demons in the area and prevents them from ever invading there again, but being completely cut off from the gods panics the area and immediately makes it fall (level 5 panic). (This is a reskin of the Nuclear Option one-time event.)

September Changes

An elven warrior has been spreading panic in a Tainted area, telling all who will listen that it's hopeless, and the Ravage will take over the world. You have to find him and stop him. New search track - look in a Tainted area with an Elven Grove, and more elves help the search.

If you find the warrior, he reveals that he was part of a conspiracy in the upper echelons of Elven government, to weaken the seals on Gaxkang's prison and cause chaos in our kingdom, then infiltrate and take over the area themselves. Not all elves are part of this plot, but one elven character is a traitor sent to sabotage us. The Ravage's strength, and the Taint, are both the result of elven rituals empowering the demons.

You find and execute the traitor, then turn to stopping the elven conspiracy. You throw out the "establish Elven Groves in each region" objective, and replace it with "destroy two Elven Groves". You gain a new Sabotage action - if you're in the area of an Elven Grove, and have the artifact matching the area, you can spend it to tear down the Grove.

The warrior's testimony gives the Demon-Hunter the information about which group has been helping the Ravage.

Also, the Demonologist has recovered from her injuries, and now joins your party.

  • (Human) Demonologist (previously Virologist): Can spend an artifact of the Ravage's color to destroy one Ravage demon anywhere in the world. Can spend the artifact matching the area they're in to destroy all Ravage demons in the area.

At the end of the game, now that the elven conspiracy has been revealed, they're openly invading. You must add a permanent Elven Grove to the first area that you add demons to during setup.

October Changes

The search for the last piece of the puzzle is on - the Demon-Hunter needs to find the original source of the Taint. New search track - look in the original Tainted city, with an Elven Grove in the city helping the search. If you complete the search, you find the wellspring of the Taint - an ancient elven temple ruin, abandoned for centuries.

If you've found all the information the Demon-Hunter needs, the elven conspiracy is finally clear. Centuries ago, these were elven lands. Humans coveted them and invaded, driving the elves out and eventually founding your kingdom. The elven gods at that time were local theurges, bound to the land, and were left behind when the elves fled. This god, though diminished, still dwelled among the temple ruins, at least until the conspirators bound him and twisted his power to their devious ends, empowering the Ravage and creating the Taint.

With this information, the Demon-Hunter can craft the weapons needed to fight the Ravage - charms against the ancient god that empowers them. You open Box 7 and get a number of new things:

  • Enchanting Halls (previously Vaccine Factories) - these new structures generate one charm per turn. They follow the same rules as Guildhalls: they can only be built in non-rioting cities, and humans can teleport between two Enchanting Halls, or between them and Guildhalls.
  • Charms (previous Vaccine Doses) - charms are produced at Enchanting Halls. A player in the same city as some charms can pick up as many as they want as an action.
  • Cleanse action (previously Immunize) - if a player has one or more charms, they can Cleanse the area they're in. If there are any Ravage in the area, this kills one Ravage demon. Otherwise, it cleanses the area, permanently removing the Taint and preventing it from returning. Ravage demons are never placed in Cleansed areas. (Cleansed cities in non-Ravage colors revert to generating normal demons of their color; cleansed Ravage-color cities no longer generate anything at all.) Cleansing takes an additional action for each Elven Grove in the area, as the charms have to fight harder against the stronger background of elven magic.
  • Demon-Hunter (previously Immunologist) - this skilled demon-slayer can wield the charms more effectively than anyone else. When he Cleanses, he can kill all the Ravage demons in an area by spending multiple charms (one per Ravage), similar to how the Paladin kills multiple demons with a single Fight action. Once per turn, he can Cleanse an adjacent area.

With the tools to fight the Ravage now in your hands, it's no longer important to just contain them - destroy the "Quarantine 7 Tainted cities" objective. In return, you gain two new objectives: "Have 3 Enchanting Halls Built", and "Cleanse 6 Tainted Cities". After achieving the latter one, tear it up at the end of the game.

November Changes

Nothing new introduced, just an ominous warning from the Elven Conspiracy that they're on to you.

At the end of November, if you hadn't yet opened boxes 6 and 7, a secondary team of adventurers has found the information for you, so you can open the boxes now.

December Changes

The Elven Conspiracy is taunting you - even if you do defeat the Ravage, they're boring directly into Gaxkang's prison as they speak. All your work is for nothing if the Lich-lord is released!

At this point, you tear up all your current objectives. Instead you now have two mandatory objectives: Cleanse all Tainted cities, and bind Gaxkang permanently. No half-measures this time!

Things That Aren't Other Things, Monad Edition

Last updated:

An intuitive example of something that's a Functor but not an Applicative is a tagged value, where you have a value that's associated with some arbitrary "tag". This is a functor - given a function f and a tagged value ("tag1", v), you can map over it to get the result ("tag1", f(v)). But it's not an applicative: for one, there's no "default" tag, so it's not Pointed, but also given ("tag1", f) and ("tag2", v), the result should have a value of f(v), but what would the tag be? There's no automatic way to generate a tag from two existing tags.

An intuitive example of something that's an Applicative (a Traversable, really) but not a Monad is a ZipList. Given [f1, f2] and [v1, v2, v3], you'll end up with [f1(v1), f2(v2)], so it's Applicative. But implementing join is impossible in general - given [[a1, a2], [b1], [c1, c2, c3]], the obvious way to collapse it into a single ziplist is to take the Nth element of the Nth list. But there's no b2 element! Fixed-length or infinite-length ziplists are Monads, though. (Why can't you just stop at the first one that fails, returning [a1]? It violates the monad laws! Usually associativity, depending on exactly what you implement.)

Needed: an intuitive example of something that's Applicative but not Traversable.

Interesting side-note: the Writer monad is almost identical to the tagged value - it's conceptually a value paired with a "logging value". The difference is that the logging value is not arbitrary - it's required to be a monoid. This gives us a default value, so it's Pointed, and a way to combine logging values, so ("log1", f) and ("log2", v) can be combined into ("log1log2", f(v)). So Writer is Applicative, and in fact a full Monad.

Creating ASCII-Sortable Strings from Numbers, Redux

Last updated:

(I wrote about this a while ago. I'm revisiting it for fun, and to run thru it in a more reasonable order.)

Sometimes you have a lot of files with numerical names, and you want to name them such that a stupid ASCIIbetical sort (that is, something that sorts letter-by-letter according to ASCII ordering) will sort them in order. Just numbering them won't work right - "10.txt" will come between "1.txt" and "2.txt". Zero-padding works for most practical purposes, but it requires you to guess how many entries there will be ahead of time, and come on now.

What if you had no idea how many files there were going to be, but maybe it'll be a whole bunch? Then there are a few schemes that'll work.

Unary Numbers

First, let's talk about unary numbers. Normal numbers are "positional" - the value of a digit depends on its position in the number. Unary numbers are non-positional, so digits are worth the same no matter where they are. You already know of one common type - Roman numerals. An X is always worth 10 no matter where it shows up.

It turns out that, if they satisfy two criteria, numbers written in unary always sort correctly, no matter how long they are. These criteria are:

  1. The number must be written in "greedy form", where you always use as many of the largest possible digit first, before trying any smaller digits. For example, in Roman numerals 16 must be written XVI, not VVVI or XIIIIII.
  2. The digits must sort correctly themselves. Roman numerals do not do this - I, V, and X sort correctly, but C (100) goes before all of them.

Here's a simple system that does satisfy those two constraints: the letters A-Z, where A stands for 1, B for 2, etc until Z is for 26. Then a number like 100 is written ZZZV. I'll use this for the rest of the post.

The problem with unary numbers is that they grow linearly with the size of the number. If I double the number, I double the length of the number too. The length of positional numbers grow with the log of the value, which is much much smaller - doubling the value of a binary number only increases its length by 1.

So we can't quite have everything. Positional numbers grow slowly, but don't sort right. Unary numbers sort right, but grow quickly. In combination, tho!

Unary Prefix

Positional numbers do sort correctly as long as they have the same length. All 2-digit numbers (10-99) will correctly sort themselves ASCIIbetically, for example. This is why zero-padding works - it changes all the numbers to be the same length.

By taking advantage of this, we can get the best of both worlds. The first way to write numbers that'll sort correctly as they get infinitely large is to write the number normally, then prefix it with an unary number listing its length.

For this to work correctly, the unary number must use a different set of digits from the "normal" number, and those digits must sort after the "normal" digits. These are both true of the A-Z system.

So, the number 5 is written "A5". The number 99 is written "B99", while 100 is "C100". All 2-digit numbers will sort after the 1-digit numbers (because B > A) but before the 3-digit numbers (because B < C), and whenever 2-digit numbers are compared, they'll match on their B digit and then sort correctly on their remaining value.

This system adds a single character of overhead for all numbers below 10^27, or two for more numbers below 10^53, more than long enough for all practical purposes. But what if we want really big numbers? A 100-digit number will have 4 digits of overhead. A 1000-digit number will have 39. A million-digit number will have almost 40 thousand characters of overhead. Can we do better?

Of course.


So our problem is that the prefix grows linearly with the length of the number (the log of the value). This is fine for normal numbers, but if you get really big numbers, the prefix gets too big. Unary numbers just aren't good at representing large numbers, you need positionals for that. But how do we integrate more positional values without running into the sorting problem again?

Recall what the purpose of the prefix was - to correctly sort the strings so that they were grouped with same-length prefixes that sorted properly even tho they were positional. Assigning each set of suffixes to a different group can be done fine with any number, so let's use positional numbers for it instead, so 1 million (1000000, 7 digits long) is written as "71000000".

Now we're back in the positional hole, because a 10-digit number like 1 billion will start with a "10" prefix, and sort before 1 million. So let's return to unary, and use it to sort the prefixes into correctly-sorting same-length groups - if the prefix is 1 digit, further prefix it with an A, if the prefix is 2 digits, further prefix it with a B, etc. With this, 1 million is written "A71000000", and 1 billion is written "B101000000000", and everything sorts correctly again.

This forces us to pay a little bit extra prefix tax for small numbers - ten is written "A210", whereas before it was just "B10". But it quickly overcomes the previous scheme - previously, a 10^105 was "ZZZZA10000..." (5 chars of prefix) while now it's "C10510000..." (4 chars of prefix). A million-digit number previously had about 40 thousand chars of prefix, while now it has 8 ("G1000000"). That's great! But what if our numbers are really big - can we do even better?

Of course.

Stacks On Stacks

A million digit number (like 10^million) is handled great by the previous form, and is so stupendously large that there's no possible way any larger sorting scheme could be needed. But we're just playing around here - what if we wanted to encode a number like 10^10^million? Then the positional prefix is a million characters long, and the unary part is another 40 thousand.

The obvious solution here is to just repeat the previous step: have the second prefix be positional as well, and then have the final unary prefix be the length of the second prefix. So our 10^10^million number's prefix would be "A710000...", a million and two characters long.

But that's kinda inelegant. It's thematically similar to zero-padding - you have to guess ahead of time how many levels of prefix you need. If you guess too low, the excess gets soaked up into a big unary prefix (10^10^10^million would still have a 40k unary prefix). If you guess too high, you end up saddling "low" numbers with several levels of unnecessary prefix (ten is written as "A1210").

Instead, let's produce prefix levels dynamically. Start with the original prefix - the length of the number, written positionally. If this prefix is 2 or more chars long, add another prefix level indicating how long it is. Repeat as needed until you get to a single-char prefix. At the end we still need an unary prefix to make it all work, but we'll encode the number of levels with it. It's a little non-obvious, but you can show without too much difficulty that this correctly sorts all numbers.

So, a few of the numbers we were talking about previously:

  • ten: "A210" (2 prefix chars)
  • billion: "B2101000000000" (4 prefix chars)
  • 10^million: "B71000000100000..." (9 prefix chars)
  • 10^10^million: "C71000000100000...10000....." (1000009 prefix chars)

For any finite number of levels, the total prefix length is: log^1 + log^2 + ... + (log^N)*2, where N is the number of levels.

In our final scheme, the length is instead: log^1 + log^2 + ... + log^N + N, where N is the log* of the number. log* is the iterated log, the number of times you have to apply the log function before you hit <=1. log* stays ridiculously small for all numbers you can conceive of.

Can we go further? Yeah, of course. For unbelievably huge numbers, the number of levels might get large enough for the unary prefix to start mattering. In that case, you can extend it to use multiple positional levels to indicate the number of levels, with the unary prefix indicating the number of those super-levels. But extensions are very mechanical at this point, and only matter for numbers so large that they're difficult even write down. Stopping at this level is good enough for anything ever in reality.

How To Pronounce Hexadecimal Numbers

Last updated:

A few months back, <> posted a guide on how to pronounce hex numbers, inspired by a funny scene from Silicon Valley.

The basics of any English-like number-naming system are:

  1. Digit names.
  2. "Teen" names, for numbers of the form 1x.
  3. "Xty" names, for numbers of the form x0.
  4. Place names, for grouping larger sets of numbers, like "hundred", "thousand", and "million".

When hex numbers are composed of just decimal digits, #1-#3 are identical to decimal numbers. "45" is still read as "forty five". Similarly, if a "letter" digit shows up in the ones place, it's just read as the letter - "4B" is "forty bee". (The full set of English spellings is "ae", "bee", "cee", "dee", "ee", and "eff".)

Xty names

The Silicon Valley clip provided an example for #3 - "fleventy", presumably for Fx numbers. Bzarg expanded that out to a full set of six:

  • A0: atty
  • B0: bibbity
  • C0: city
  • D0: dickety
  • E0: ebbity
  • F0: fleventy

So a number like "f5" is read as "fleventy five", or "BC" as "bibbity cee".

Teen names

They extrapolated those into #2, teen names, too:

  • 1A: abteen
  • 1B: bibteen
  • 1C: cleventeen
  • 1D: dibbleteen
  • 1E: eggteen
  • 1F: fleventeen

(These are hard because of the near-homonyms of "A" and "8", and "C" and "6". I like the names they ended up with!)

Place names

Finally, they suggested a place name for when you go past two digits - "bitey" (because two hexits comprise a byte). So "100" is pronounced "one bitey", "ABC" is "ae bitey bibbity cee", etc.

They don't suggest in the post what higher place names should be, but they do implement some in the spelling toy - "halfy" for 4 digits, and "worddion" (with a spread-out, j-like "dd", to blend into the "yun" sound at the end of "million") for 8. They reuse "halfy" for 6, which seems confusing and probably unintentional.

I don't like these, tho. The size of a "word" changes over time, to match the "size" of the processor - it used to be 2 bytes, and it's commonly 8 bytes these days, rather than the 4 bytes suggested by the naming toy. This also doesn't extend well.

I suggest instead adopting a scheme vaguely similar to English, but with a splash of newness. Start with "bitey" for a byte, but at four hexits, use "bi-bitey", similar to "billion". Then at 6, combine them to be "bi-bitey bitey", and only at 8 do you introduce a new "tri-bitey" term. Continue combining for 10 (tri-bitey bitey), 12 (tri-bitey bi-bitey), and 14 (tri-bitey bi-bitey bitey), and at 16 go up to quad-bitey.

In general, you group into bytes, find the index of the place you're trying to name, and write it in binary - the 1s get pronounced with an appropriate name. So, 12 hexits is 6 bytes. 6 in binary is 110. The third and second bits are 1, so the name for that place is "tri-bitey bi-bitey".

This system extends for quite a while, and covers all the numbers we'll ever really have to worry about. It also has a nice extra-binary feel to it, which I like.

I'll put together a naming toy for this in a bit and link it into here.