base10-mul.sed: Multiply two base-10 numbers using regexes

This was a pretty fun hack---it looks esoteric and sounds impressive (or
even impossible to some), but is really easy given the proper
intuition.  Perhaps at some point I'll write more on DFAs and some of the
theory and limitations behind this, but for now, that's beyond the scope of
what I had intended for this repo.  (I suspect that my intentions are
shifting.)

Enjoy!

* regex/base10-inc.sed: Reference new script.
* regex/base10-mul.sed: New script.
master
Mike Gerwitz 2018-11-23 00:21:03 -05:00
parent 107d090a04
commit 338bb0cdf0
Signed by: mikegerwitz
GPG Key ID: 8C917B7F5DC51BA2
2 changed files with 176 additions and 3 deletions

View File

@ -23,9 +23,9 @@
# for replacements are only sufficient for M≤3 when K=1; what do we have to
# do when M>3 to replace only the digits we are interested in?)
#
# It is possible to subtract using the same concept. Further, since we can
# implement addition and subtraction, we can also implement multiplication
# and division.
# It is possible to subtract using the same concept (see `base10-mul.sed').
# Further, since we can implement addition and subtraction, we can also
# implement multiplication and division.
#
# TO USE: Provide a 0-padded 3-digit base-10 number. Each time this script
# is run, the number will be incremented by one. Exits with exit code 1 if

View File

@ -0,0 +1,173 @@
# Single step in multiplying two base-10 numbers using only regexes
#
# Copyright (C) 2018 Mike Gerwitz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This script demonstrates how to multiply two 0-padded 3-digit base-10
# numbers using nothing more than a series of regular expressions. It
# involves adding and subtracting from a series of registers---each
# invocation of the script performs a single addition step toward the final
# result.
#
# For a lighter introduction, first look at `base10-inc.sed', which shows
# how to implement a basic increment on a single 3-digit base-10 number.
#
# This script uses four 3-digit registers delimited by a single space (for
# legibility; no delimiters are necessary since the registers are
# fixed-width). They are laid out as follows (extra spaces added for
# illustration purposes):
#
# 001 002 003 004
# 1st operand/ 2nd operand copy of accumulator src
# accumulator 1st operand (decrementing)
#
# The first and second registers hold the first and second operands (the two
# numbers to multiply). The first register doubles as the accumulator---it
# will hold the result of the calculation at each step. The third register
# is always a copy of the first operand; this is necessary since the first
# register is always changing. (Alternatively we could have made the third
# register the accumulator, but that makes the regexes a little messier.)
# The fourth register is the value that the accumulator takes _from_---it
# initially is a copy of register three and is decremented at each
# step. The accumulator is incremented at each step.
#
# Once register four reaches zero, it is reset to the value of register
# three and register two is decremented. Once register two reaches `001',
# the calculation is finished and all but the first register (holding the
# accumulated result) are discarded.
#
# This script is designed to be run in a loop, allowing each step of the
# process to be observed, saved, and manipulated. This could easily be
# changed to use sed's branching features to loop and produce the result in
# a single invocation, but that makes introspection difficult (and the
# output a whole lot less interesting).
#
# TO USE: Provide two 0-padded base-10 integers delimited by a space, or a
# previous state of the system (its output). For example, to perform 5*3,
# provide this initial input:
#
# 005 003
#
# The output of the first invocation will be:
#
# 006 003 005 004
#
# To invoke with the animation script, you might do:
#
# $ ./animate base10-mul.sed <( echo 005 003 )
#
# The process is further detailed below. Have fun!
##
# Input must begin with two 3-digit base-10 numbers. We'll ignore trailing
# data for now, since we will be using that as working memory.
/^[0-9]\{3\} [0-9]\{3\}/!q1
# If we do not have more than two numbers, then this is the first time we're
# being run and we need to set up our working memory. The leftmost number
# (the first operand) is going to serve as our accumulator, so we will
# duplicate that number. We also need to hold a copy of that operand to
# decrement as we increment the accumulator. So we start with three copies
# of the first operand in the form of `A B A A'.
s/^\([0-9]\{3\}\) .\{3\}$/& \1 \1/
# As a special case, if the second operand is `000', then the result
# is `000'. Set ourselves up so that the next check will terminate.
s/^\(...\) 000/000 001/
# If we have no more iterations to perform (if the second operand in
# register two is `001'), then we are done! If that's the case, clean up
# our output to display only the final result. The script will
# automatically exit with a non-zero status the next iteration because of
# the guard at the top of this script. The trailing spaces ensure that
# leftover output from previous iterations is erased in the animation
# script.
s/^\(...\) 001.*/\1 /
/^... \+$/b
# We will be taking numbers from the last register. Increment the
# accumulator.
s/^\(..\)9/\1A/; s/^\(..\)8/\19/; s/^\(..\)7/\18/;
s/^\(..\)6/\17/; s/^\(..\)5/\16/; s/^\(..\)4/\15/;
s/^\(..\)3/\14/; s/^\(..\)2/\13/; s/^\(..\)1/\12/;
s/^\(..\)0/\11/;
# Accumulator 10s.
s/^\(.\)9A/\1A0/; s/^\(.\)8A/\190/; s/^\(.\)7A/\180/;
s/^\(.\)6A/\170/; s/^\(.\)5A/\160/; s/^\(.\)4A/\150/;
s/^\(.\)3A/\140/; s/^\(.\)2A/\130/; s/^\(.\)1A/\120/;
s/^\(.\)0A/\110/;
# Accumulator 100s
s/^9A/A0/; s/^8A/90/; s/^7A/80/;
s/^6A/70/; s/^5A/60/; s/^4A/50/;
s/^3A/40/; s/^2A/30/; s/^1A/20/;
s/^0A/10/;
# We now need to _decrement_ the last register (since the accumulator took a
# single number from it). This is very similar to incrementing, but instead
# of setting a carry flag on overflow, we set it on an _underflow_.
s/\(..\)0$/\1A/; s/\(..\)1$/\10/; s/\(..\)2$/\11/;
s/\(..\)3$/\12/; s/\(..\)4$/\13/; s/\(..\)5$/\14/;
s/\(..\)6$/\15/; s/\(..\)7$/\16/; s/\(..\)8$/\17/;
s/\(..\)9$/\18/;
# Accumulator 10s.
s/\(.\)0A$/\1A9/; s/\(.\)1A$/\109/; s/\(.\)2A$/\119/;
s/\(.\)3A$/\129/; s/\(.\)4A$/\139/; s/\(.\)5A$/\149/;
s/\(.\)6A$/\159/; s/\(.\)7A$/\169/; s/\(.\)8A$/\179/;
s/\(.\)9A$/\189/;
# Accumulator 100s. Note that we do not support going below 0.
s/1A\(.\)$/09\1/; s/2A\(.\)$/19\1/; s/3A\(.\)$/29\1/;
s/4A\(.\)$/39\1/; s/5A\(.\)$/49\1/; s/6A\(.\)$/59\1/;
s/7A\(.\)$/69\1/; s/8A\(.\)$/79\1/; s/9A\(.\)$/89\1/;
# If we end up with `000', then we have performed one multiplication
# step. Decrement the second operand (in the second register) to reflect
# this progress.
s/^\(... ..\)0\(.* 000\)/\1A\2/; s/^\(... ..\)1\(.* 000\)/\10\2/;
s/^\(... ..\)2\(.* 000\)/\11\2/; s/^\(... ..\)3\(.* 000\)/\12\2/;
s/^\(... ..\)4\(.* 000\)/\13\2/; s/^\(... ..\)5\(.* 000\)/\14\2/;
s/^\(... ..\)6\(.* 000\)/\15\2/; s/^\(... ..\)7\(.* 000\)/\16\2/;
s/^\(... ..\)8\(.* 000\)/\17\2/; s/^\(... ..\)9\(.* 000\)/\18\2/;
# Second register 10s.
s/^\(... .\)0A\(.* 000\)/\1A9\2/; s/^\(... .\)1A\(.* 000\)/\109\2/;
s/^\(... .\)2A\(.* 000\)/\119\2/; s/^\(... .\)3A\(.* 000\)/\129\2/;
s/^\(... .\)4A\(.* 000\)/\139\2/; s/^\(... .\)5A\(.* 000\)/\149\2/;
s/^\(... .\)6A\(.* 000\)/\159\2/; s/^\(... .\)7A\(.* 000\)/\169\2/;
s/^\(... .\)8A\(.* 000\)/\179\2/; s/^\(... .\)9A\(.* 000\)/\189\2/;
# Second register 100s. Note that we do not support going below 0.
s/^\(... \)1A\(.* 000\)/\109\2/; s/^\(... \)2A\(.* 000\)/\119\2/;
s/^\(... \)3A\(.* 000\)/\129\2/; s/^\(... \)4A\(.* 000\)/\139\2/;
s/^\(... \)5A\(.* 000\)/\149\2/; s/^\(... \)6A\(.* 000\)/\159\2/;
s/^\(... \)7A\(.* 000\)/\169\2/; s/^\(... \)8A\(.* 000\)/\179\2/;
s/^\(... \)9A\(.* 000\)/\189\2/;
# Otherwise, we must then prepare for the next round by copying the third
# register (the original first operand) back into the fourth.
s/\(...\) 000$/\1 \1/