Formula Solver
1992 · C:\FORM_X\FORM_X.EXE
Loading FORM_X.EXE, 4 KB
A paid challenge: exact 20-digit math, written by hand in assembly for a PC with no math chip. Type a formula in x and get a table of answers.
The story
FORM_X is his answer to a paid challenge: exact decimal math in pure assembly, with no math chip. He built a whole formula solver from scratch. A keyboard filter lets only valid formulas in. A solver works from the innermost brackets out. And his own arithmetic handles 20-digit decimal numbers, one digit per byte. Because it was paid work, he signed it in a way that is hard to remove. His name hides in the code, scrambled, and comes back only while the program runs.
Type a formula. Watch him solve it.
In 1992 someone challenged Yasser, and offered to pay him: do exact decimal math on a PC with nothing but assembly. No math chip, no library. His answer was FORM_X, 3,546 bytes on disk.
You give it a formula in x, a first value and a last one, and it prints a table of ten results. Below, his program runs in your browser, ported from his code routine by routine. Over 349 test formulas it printed the very same screen as his original, every time. Pick a row under his table and watch his method: innermost brackets first, then times and divide, then plus and minus. Every result goes back into the formula until one number is left.
His filter guards the typing: a key he would refuse never reaches the formula.
F(x)=((x-3)*(x+2)/7)
For a modern developer it is a find-and-replace loop on a string: find the innermost brackets, replace them with their value, repeat. He did it on raw bytes, with no parser and no regular expressions. And every number he stores is exactly 20 bytes long. So the two numbers around any sign always sit 20 bytes to its left and 20 bytes to its right. He never has to search for where a number begins.
Try 0.1+0.2. His program prints 0.300000, exactly. JavaScript, running in your browser right now, prints 0.30000000000000004, because it stores numbers in binary. His are decimal, digit by digit, all the way down.
For engineers: the solver in his assembly
push si ; SI points at a "(" inc si / mov al,[si] ; walk right cmp al,')' / je 0A69 ; a ")" closes this level cmp al,'(' / jne 0A59 call 0A58 ; a "(" goes one level deeper: the routine calls itself mov di,si / pop si ; SI and DI: this bracket call 0AA4 ; solve it and write the number back mov di,0DC3h / rep movsb ; copy the inside to scratch, end it with $ cmp byte [si],'-' ; starts with a minus? flip the first number lodsb / cmp al,'/' ... '*' ; pass 1: the leftmost * or /, again and again lodsb / cmp al,'-' ... '+' ; pass 2: the leftmost + or - Twenty digits, by hand
He could not use the math chip, and the PC had no number that kept decimals exact. So he built his own: 20 decimal digits, one digit per byte, 14 before the point and 6 after. Then he taught the PC to add, subtract and multiply them the way you learned at school.
Think of money stored as whole cents, but in millionths: 2.5 is stored as 00000000000002500000. Every number has the same 20 bytes, so lining up two numbers is free. Adding goes column by column, then one carry pass runs from right to left. There, a single divide by 10 gives each column its digit and its carry at once.
A negative number works like a car’s odometer rolled back past zero: −1.5 is stored with 9s at the front. The gift of that choice: to subtract, he flips a number and adds it, so one adding routine does both jobs. Dividing is long division by counting how many times the divisor fits. The processor has its own helpers for decimal math; he used none of them. Every step is his.
Up to 6 digits before the point and 6 after.
He keeps the 20 digits in the middle: 14 before the point and 6 after. 2.5 × 3.5 = 8.75
A keyboard that only says yes to a formula
While you type, FORM_X refuses every key that cannot come next. A single byte keeps track, and each of its eight bits stands for one kind of key.
It is the idea behind a form that grays out the buttons you cannot press yet, and it costs one byte. After a digit, it allows another digit, a point, a closing bracket or an operator. After an operator, only a bracket, a digit or x. The top bit, marked seen, remembers that this number already has a point, so a second one is refused. Every key also saves the byte from before it, so Backspace steps back exactly. Type here, or press his keys, and watch the bits.
Type a formula. Keys he would refuse are dimmed.
The byte that decides, one bit per kind of key
Decimal 46 Hex 2Eh
Only valid keys ever reach the formula, so his solver can trust every formula it gets. When you press Enter he makes three last checks. The formula must end in a digit, x or a closing bracket. The brackets must balance. And no number can carry more than 7 digits before its point.
For engineers: the filter, line by line
mov ah,7 / int 21h ; wait for a key, show nothing yet mov bl,[0282h] / mov bh,bl ; the filter byte cmp al,'(' / je 00C4 and bh,2 / cmp bh,0 / je 0075 ; its bit is off: ignore the key mov byte [0282h],2Eh ; after "(": ( digit x - and bh,0C0h / cmp bh,40h ; a point needs bit 40h, and no point yet and byte [0282h],80h ; after a digit: keep "this number has a point" or byte [0282h],75h ; and allow ) digit operator - . mov [si+0232h],bl ; save the old byte for Backspace Signed, and guarded
FORM_X was paid work, so Yasser signed it in a way that is hard to take out. His name hides inside his own instructions, scrambled, and three small blocks of code bring it back. Each one runs exactly once, then switches itself off.
Each block starts with a byte that does nothing, and the first thing a block does is overwrite that byte with RET, “return”. The next time anything calls it, it returns at once. Block 1 fixes five bytes of the scrambled text. Block 2 unscrambles it: each letter is its byte minus the byte stored before it.
Block 3 adds up 53 bytes of code, block 1 included. It adds that total into the program’s exit jump, which the file stores as a jump of zero bytes. Untouched, the jump lands on the code that writes his credit on the screen and ends the program. Change one byte, and it lands somewhere else. It works like a hash on a script tag: change the file, and it no longer runs.
Yasser Dot Design.. <c>1992 YASSER MOHAMED ALI.
Each letter is its byte minus the stored byte before it.
Block 3 adds up these 53 bytes of code, block 1 included, as 52 overlapping words. It adds A517h and writes the total into the exit jump. Tap any byte to change it.
formula copier block 1
- Sum
- 582Eh
- + A517h
- FD45h
- The jump
- E9 45 FD
- Lands at
- 0C5B
0C5B: his exit. It writes his credit, with his name and the year 1992, straight onto the screen, then ends the program.
| 1991 to 1993 | 2026 | Why it is the same idea |
|---|---|---|
| 20 decimal digits, one per byte, 6 after the point | SQL DECIMAL(20,6), Java BigDecimal, Python decimal | Exact decimals for money and measurement. SQL’s DECIMAL(20,6) is his exact format. |
| Odometer numbers: 9s at the front mean negative | Two’s complement in every CPU | The same trick in base 2: subtracting is adding the flipped number. |
| One byte that lets only valid keys in | Input masks and live validation | Stop bad input at the keyboard, not after the fact. |
| A checksum of his code that completes the exit jump | Code signing and Subresource Integrity hashes | Both notice a changed program and refuse to treat it as the real one. |
Every Byte of FORM_X.EXE
Each square is one byte of the program in memory, colored by what it is. Tap a part to open it in the full map, down to the single instruction.
3,546 bytes on the disk, packed by DIET
9,744 bytes in memory, 9,696 of them his own code
- Data52%
- Code39%
- Text7.1%
- Table0.9%
- Header0.3%
- Start-up code0.2%
- Fix-up list0.1%
- Empty space<0.1%
The biggest parts, his first
- Work area4,489 bytesYasser
- Results and errors563 bytesYasser
- Divide550 bytesYasser
- Key filter445 bytesYasser
- Formula set-up380 bytesYasser
- Error messages and table header369 bytesYasser
Mapped by hand from the program’s own bytes.