Start page: ARABART home
← All fifteen programs

Formula Solver

1992 · C:\FORM_X\FORM_X.EXE

C:\FORM_X\FORM_X.EXE
Formula Solver running in DOS

Loading FORM_X.EXE, 4 KB

Click the screen to type into the program.

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.

HIS METHOD one row, step by step
formula
-0.321428
this bracket
nothing yet
his table prints -0.321428
answer
99999999999999678572
He copies your formula into his work area, inside one more pair of brackets of his own, and writes every number and every x as 20 digits. Here x is −1.5.
He reads from the left to the first closing bracket. Its pair holds no other brackets, so he solves it first.
No × or ÷ is left in this bracket, so now plus and minus, the leftmost one first: −1.5 − 3 = −4.5.
He writes the single number back over the whole bracket and closes the gap. The formula just got shorter.
He reads from the left to the first closing bracket. Its pair holds no other brackets, so he solves it first.
No × or ÷ is left in this bracket, so now plus and minus, the leftmost one first: −1.5 + 2 = 0.5.
He writes the single number back over the whole bracket and closes the gap. The formula just got shorter.
Only his own outer brackets are left. The whole formula is one flat bracket now.
Times and divide come first, the leftmost one first: −4.5 × 0.5 = −2.25. The result takes the place of both numbers and the sign.
Times and divide come first, the leftmost one first: −2.25 ÷ 7 = −0.321428. The result takes the place of both numbers and the sign.
He writes the single number back over the whole bracket and closes the gap. The formula just got shorter.
One number is left: the answer, −0.321428. His table prints it with 6 decimals.
FORM_X.EXE his screen
** THE RESULT TABLE OF --- F(X) **F(x)=((x-3)*(x+2)/7) Value of X Value of F(X) ------------------------------------------------------------------------- -1.500 ----------------------> -0.321428 -1.000 ----------------------> -0.571428 -0.500 ----------------------> -0.750000 0.000 ----------------------> -0.857142 0.500 ----------------------> -0.892857 1.000 ----------------------> -0.857142 1.500 ----------------------> -0.750000 2.000 ----------------------> -0.571428 2.500 ----------------------> -0.321428 3.000 ----------------------> 0.000000

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
FORM_X.EXE 016A:0A58 and 0AA4, condensed
0A58 push si ; SI points at a "("
0A59 inc si / mov al,[si] ; walk right
0A5C cmp al,')' / je 0A69 ; a ")" closes this level
0A60 cmp al,'(' / jne 0A59
0A64 call 0A58 ; a "(" goes one level deeper: the routine calls itself
0A69 mov di,si / pop si ; SI and DI: this bracket
0A6C call 0AA4 ; solve it and write the number back
0AB7 mov di,0DC3h / rep movsb ; copy the inside to scratch, end it with $
0AC2 cmp byte [si],'-' ; starts with a minus? flip the first number
0AEB lodsb / cmp al,'/' ... '*' ; pass 1: the leftmost * or /, again and again
0B06 lodsb / cmp al,'-' ... '+' ; pass 2: the leftmost + or -
The whole formula sits inside one more pair of brackets that he adds himself, so the last bracket solved is the formula, and its value is the answer.

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.

FORM_X.EXE one digit per byte
2.5 00000000000002500000
× 3.5 00000000000003500000
3.5 × 2 610
3.5 × 5 1525
total 00000000000008750000
answer 00000000000008750000

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.

FORM_X.EXE the key filter
F(x)=

Type a formula. Keys he would refuse are dimmed.

The byte that decides, one bit per kind of key

seen
.
-
*/+
x
0-9
(
)

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
FORM_X.EXE 016A:0075, condensed
0075 mov ah,7 / int 21h ; wait for a key, show nothing yet
0079 mov bl,[0282h] / mov bh,bl ; the filter byte
007F cmp al,'(' / je 00C4
00C4 and bh,2 / cmp bh,0 / je 0075 ; its bit is off: ignore the key
00CC mov byte [0282h],2Eh ; after "(": ( digit x -
011A and bh,0C0h / cmp bh,40h ; a point needs bit 40h, and no point yet
0138 and byte [0282h],80h ; after a digit: keep "this number has a point"
013D or byte [0282h],75h ; and allow ) digit operator - .
014D 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.

FORM_X.EXE his name, inside the code
20
40
60
80
D9Y
3Ba
AEs
22s
87e
F9r
1A
5ED
CDo
42t
62
A6D
0Ce
7Fs
E8i
50g
BEn
EC.
1B.
3B
5B
7B
9B
D7<
3Bc
79>
AA1
E39
1D9
4F2
6F
C8Y
0AA
5DS
B0S
F5E
48R
68
B5M
05O
4DH
8EA
DBM
21E
65D
85
C6A
13L
5CI
8A.

Yasser Dot Design.. <c>1992 YASSER MOHAMED ALI.

Each letter is its byte minus the stored byte before it.

Block 1 · 036E RET Block 2 · 0A6F RET Block 3 · 0B73 RET
BLOCK 3 the checksum that steers the exit

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.

Then and now
1991 to 19932026Why 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.

C:\FORM_X\FORM_X.EXE

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

  1. Work area4,489 bytesYasser
  2. Results and errors563 bytesYasser
  3. Divide550 bytesYasser
  4. Key filter445 bytesYasser
  5. Formula set-up380 bytesYasser
  6. Error messages and table header369 bytesYasser

Mapped by hand from the program’s own bytes.

Open in the byte mapSee the packed file