59

Is it possible to perform some operations on variables in a makefile? For instance, defining

JPI=4
JPJ=2

is it possible to define in the same makefile a variable JPIJ equal to the expanded value of $(JPI)*$(JPJ)?

Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
malaboca
  • 591
  • 1
  • 4
  • 3

9 Answers9

47

Using Bash arithmetic expansion:

SHELL=/bin/bash
JPI=4
JPJ=2
all:
    echo $$(( $(JPI) * $(JPJ) ))

The first line is to choose the Bash shell instead of the default (sh). Typically, sh doesn't support arithmetic expansion. However in Ubuntu, /bin/sh is provided by Dash, which supports this feature. So that line could be skipped.

The double dollar sign is because we want the expansion to be done by the shell. Note: the JPI and JPJ variables are expanded by make first, then the expression is passed to bash like this:

$(( 4 * 2 ))
Flow
  • 23,572
  • 15
  • 99
  • 156
Dominic
  • 619
  • 5
  • 4
  • 10
    POSIX `sh` does indeed support [arithmetic expansion](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_04). – Søren Løvborg Jan 14 '14 at 13:02
  • 2
    +1 as this is the best solution because it only depends on a POSIX compatible shell. And thanks for pointing out that we need a double dollar sign when doing shell arithmetic in make. – Flow Feb 02 '14 at 13:45
21

Answer from @mrkj is great but as @Daniel mentions, not all systems have bc (for example, I don't have it on MSys).

I have found the two following methods, both using shell: $$(( ... )) and expr ...

JPI=4
JPJ=2

#With Double-dollar
JPIJ_1 = $(shell echo $$(( $(JPI) + $(JPJ) )))

#With 'expr'
JPIJ_2 = $(shell expr $(JPI) + $(JPJ) )

$(info Sum with Double-$$: $(JPIJ_1))
$(info Sum with 'expr': $(JPIJ_2))

Note that when using expr, you shall put spaces around the + or it will return 4+2. This is not required when using $$.

.

When you have bc available, you might definitely go with it. I found the following page very interesting: http://www.humbug.in/2010/makefile-tricks-arithmetic-addition-subtraction-multiplication-division-modulo-comparison/

Jean-Francois T.
  • 11,549
  • 7
  • 68
  • 107
  • Beware that `expr ...` is executed by your shell and, thus, is subject to name expansion, e.g. `expr 2 * 3` will cause an error instead of the expected output because `*` is expanded by the shell to all file names in the current directory. You will need to either escape the operator `expr 2 \* 3` or put the whole expression in single quotes `expr '2 * 3'`. – Arne L. Nov 03 '21 at 16:03
17

If you're using GNU make and have bc installed on your system, you can use something like this:

JPI=4
JPJ=2
FOO=$(shell echo $(JPI)\*$(JPJ) | bc)
all:
  echo $(FOO)
mrkj
  • 3,041
  • 19
  • 24
  • 7
    expr is more common than bc: `$(shell expr $(JPI) \* $(JPJ))` – Daniel Alder Jan 09 '15 at 12:07
  • @DanielAlder: Interesting, I always use `bc` but I can see how `expr` is possibly more convenient if you're doing simple base 10 integer arithmetic (which is the case for this question). – mrkj Jul 14 '15 at 01:42
11

It's clumsy (or brilliant, depending on your perspective), but you can do arithmetic directly in GNU make. See Learning GNU Make Functions with Arithmetic. Be aware though that this method doesn't scale well. It will work wonderfully for small numbers as you have shown in your question, but it doesn't do well when you're working with numbers with a large magnitude (greater than 10,000,000).

Eric Melski
  • 16,432
  • 3
  • 38
  • 52
  • 1
    hmmm.... its surprising to see makefiles can do math. Shouldn't this be a easy feature to add ? – Kamath Aug 24 '11 at 06:30
  • @Kamath take a look at [gmtt](https://github.com/markpiffer/gmtt) to find an implementation for big numbers (~63 places but you could have more if you want). The code is still clumsy or maddening or awkward or scary, depending on your perspective, tho – Vroomfondel Nov 07 '18 at 08:38
9

In GNU Make with Guile support (i.e. since version 4.0) it is easy to use call to Scheme language for arithmetic or other calculations. It is done without creating any subshell or child process.

An example

JP-I := 4
JP-J := 2
JP-IJ := $(guile (* $(JP-I) $(JP-J) ))

$(info JP-IJ = $(JP-IJ) )
# prints: JP-IJ = 8

See also the manual for Guile Arithmetic Functions.

A possible check for Guile:

ifeq (,$(filter guile,$(.FEATURES)))
  $(error Your Make version $(MAKE_VERSION) is not built with support for Guile)
endif
ruvim
  • 7,151
  • 2
  • 27
  • 36
  • Under macOS you can install [GNU Make](https://www.gnu.org/software/make/) with [Guile](https://www.gnu.org/software/guile/) support via [MacPorts](https://www.macports.org/): `$ sudo port install gmake +guile`. – Hotschke Sep 01 '22 at 06:47
4

The GNU Make Standard Library provides integer arithmetic functions.

include gmsl

JPI = 4
JPJ = 2

JPIJ = $(call plus,$(JPI),$(JPJ))
Matthew Simoneau
  • 6,199
  • 6
  • 35
  • 46
  • 3
    Oh man... I saw this and thought GREAT, something built it without hacks. Then I noticed its a SourceForge project, and the project just hijacked the name and added some hype. – jww Jul 08 '15 at 19:12
  • @MatthewSimoneau 'GNU' – yyny Mar 26 '16 at 14:08
  • @YoYoYonnY, it is a "standard library" for GNU Make. I can see how "standard library" is a bit bold, but the "GNU Make" part is just descriptive. – Matthew Simoneau Mar 26 '16 at 20:49
0

With makepp it's much easier. You get direct access to the underlying Perl interpreter. In this case the makeperl function does variable expansion before evaluating as Perl, the perl function OTOH would only evaluate:

JPI=4
JPJ=2
JPIJ = $(makeperl $(JPI)*$(JPJ))
&echo result: $(JPIJ)

You can use the builtin &echo command outside of a rule as a statement.

Daniel
  • 521
  • 1
  • 4
  • 13
0

To add a late answer to the pool: The GNUmake table toolkit features many arithmetic functions. You can add, subtract, multiply, divide, take the modulus in base 8,10 and 16. Also there are the usual binary operations and, or, xor and not. Numbers can be around 60 digits but you can adapt this, if you need more. The code is pure GNUmake syntax and therefore portable between Windows and Unix, contrary to shell scripts - in case you want to number crunch, there may be better solutions ;) of course.

Here is an example:

include gmtt/gmtt.mk

NUMBER_A := -12392834798732429827442389
NUMBER_B := 984398723982791273498234
$(info $(call add,$(NUMBER_A),$(NUMBER_B)))
$(info $(call sub,$(NUMBER_A),$(NUMBER_B)))
$(info $(call mul,$(NUMBER_A),$(NUMBER_B)))
$(info $(call div,$(NUMBER_A),$(NUMBER_B)))
$(info $(call mod,$(NUMBER_A),$(NUMBER_B)))

Output:

$ make
-11408436074749638553944155
-13377233522715221100940623
-12199490762401735834920873237276176262117128241026
-12
-580050110938934545463581
Vroomfondel
  • 2,704
  • 1
  • 15
  • 29
0

After an hour of hair pulling, I ended up on this (assuming you got hexadecimal and decimal numbers mixed in like my use case):

JPI = 0x123
JPJ = 2
MUL = $(shell python3 -c "print($(JPI)*$(JPJ))") 
Ayberk Özgür
  • 4,986
  • 4
  • 38
  • 58