B.2 Arithmetic
In the previous section,
I noted that it is not possible to perform arithmetic in
make using only its native features. I then showed
how you could implement a simple counter by appending words to a list
and returning the length of the list. Soon after I discovered the
increment trick, Michael Mounteney posted a cool trick for performing
a limited form of addition on integers in make.
His trick manipulates the number line to compute the sum of two
integers of size one or greater. To see how this works, imagine the
number line:
2 3 4 5 6 7 8 9 10 11 12 13 14 15
Now, notice that (if we could get the subscripts right), we could
add, say 4 plus 5, by first taking a subset of the line from the
fourth element to the end then selecting the fifth element of the
subset. We can do this with native make functions:
number_line = 2 3 4 5 6 7 8 9 10 11 12 13 14 15
plus = $(word $2, $(wordlist $1, 15, $(number_line)))
four+five = $(call plus, 4, 5)
Very clever, Michael! Notice that the number line starts with 2
rather than 0 or 1. You can see that this is necessary if you run the
plus function with 1 and 1. Both subscripts will
yield the first element and the answer must be 2, therefore, the
first element of the list must be 2. The reason for this is that, for
the word and wordlist
functions, the first element of the list has subscript 1 rather than
(but I haven't bothered to prove it).
Now, given a number line, we can perform addition, but how do we
create a number line in make without typing it in
by hand or using a shell program? We can create all numbers between
00 and 99 by combining all possible values in the tens place with all
possible values in the ones place. For example:
make -f - <<< '$(warning $(foreach i, 0 1 2, $(addprefix $i, 0 1 2)))'
/c/TEMP/Gm002568:1: 00 01 02 10 11 12 20 21 22
By including all digits 0 through 9, we would produce all numbers
from 00 to 99. By combining the foreach again with
a hundreds column, we would get the numbers from 000 to 999, etc. All
that is left is to strip the leading zeros where necessary.
Here is a modified form of Mr. Mounteney's code to
generate a number line and define the plus and
gt operations:
# combine - concatentate one sequence of numbers with another
combine = $(foreach i, $1, $(addprefix $i, $2))
# stripzero - Remove one leading zero from each word
stripzero = $(patsubst 0%,%,$1)
# generate - Produce all permutations of three elements from the word list
generate = $(call stripzero, \
$(call stripzero, \
$(call combine, $1, \
$(call combine, $1, $1))))
# number_line - Create a number line from 0 to 999
number_line := $(call generate,0 1 2 3 4 5 6 7 8 9)
length := $(word $(words $(number_line)), $(number_line))
# plus - Use the number line to add two integers
plus = $(word $2, \
$(wordlist $1, $(length), \
$(wordlist 3, $(length), $(number_line))))
# gt - Use the number line to determine if $1 is greater than $2
gt = $(filter $1, \
$(wordlist 3, $(length), \
$(wordlist $2, $(length), $(number_line))))
all:
@echo $(call plus,4,7)
@echo $(if $(call gt,4,7),is,is not)
@echo $(if $(call gt,7,4),is,is not)
@echo $(if $(call gt,7,7),is,is not)
When run, the makefile yields:
$ make
11
is not
is
is not
We can extend this code to include subtraction by noting that
subscripting a reversed list is just like counting backwards. For
example, to compute 7 minus 4, first create the number line subset 0
to 6, reverse it, then select the fourth element:
number_line := 0 1 2 3 4 5 6 7 8 9...
1through6 := 0 1 2 3 4 5 6
reverse_it := 6 5 4 3 2 1 0
fourth_item := 3
Here is the algorithm in make syntax:
# backwards - a reverse number line
backwards := $(call generate, 9 8 7 6 5 4 3 2 1 0)
# reverse - reverse a list of words
reverse = $(strip \
$(foreach f, \
$(wordlist 1, $(length), $(backwards)), \
$(word $f, $1)))
# minus - compute $1 minus $2
minus = $(word $2, \
$(call reverse, \
$(wordlist 1, $1, $(number_line))))
minus:
# $(call minus, 7, 4)
Multiplication and division are left as an exercise for the reader.
|