15.2 Math Functions: add, sub, mul, div, mod, math.Round
Right, let’s talk math. Not the soul-crushing calculus from your university days, but the simple, practical kind you need to wrangle numbers on your website. Hugo provides a basic arithmetic toolkit because, let’s be honest, sometimes you need to do more than just add two strings together. These functions are your first line of defense against static, lifeless numbers.
The core operations are exactly what you’d expect: add, sub, mul, div, and mod. They do what they say on the tin. But here’s the first “gotcha” Hugo throws at you: their argument order is a bit… unconventional. You’d think sub 5 3 would give you 2, right? Nope. It gives you -2. Why? Because Hugo’s functions almost universally take their arguments in reverse Polish notation, or as I like to call it, “the argument order is backwards.”
Think of it like this: the function comes first, and its arguments flow from left to right. So sub 5 3 is interpreted as “start with 5, then subtract 3 from it.” The result is 2. If you want to subtract a number from another number, the number being subtracted from is the first argument. It’s counter-intuitive if you’re coming from a standard programming language, but you get used to it. Just consider it Hugo’s charming quirk.
{{/* These look weird but work correctly */}}
<p>5 + 3 = {{ add 5 3 }}</p> → <p>5 + 3 = 8</p>
<p>5 - 3 = {{ sub 5 3 }}</p> → <p>5 - 3 = 2</p>
<p>5 * 3 = {{ mul 5 3 }}</p> → <p>5 * 3 = 15</p>
<p>15 / 3 = {{ div 15 3 }}</p> → <p>15 / 3 = 5</p>
<p>16 mod 3 = {{ mod 16 3 }}</p> → <p>16 mod 3 = 1</p>
{{/* This is the classic mistake */}}
<p>I expected 2, but Hugo gave me: {{ sub 3 5 }}</p> → <p>I expected 2, but Hugo gave me: -2</p>
The Integer Division Problem
Here’s a big one. div performs integer division when both operands are integers. This isn’t a bug; it’s a consequence of Hugo being built on Go, which behaves the same way. If you {{ div 10 4 }}, you won’t get 2.5. You’ll get 2. It truncates the decimal part completely. For a content management system that might be calculating averages or percentages, this is, frankly, a bit savage.
The workaround? Force at least one of the operands to be a float by using float or adding .0. This tells Hugo’s Go-based templates to perform floating-point division instead.
{{/* The frustrating, truncated way */}}
<p>Price per unit (wrong): ${{ div 100 3 }}</p> → <p>Price per unit (wrong): $33</p>
{{/* The correct, precise way */}}
<p>Price per unit (right): ${{ div (float 100) 3 }}</p> → <p>Price per unit (right): $33.333333333333336</p>
<p>Price per unit (right): ${{ div 100.0 3 }}</p> → <p>Price per unit (right): $33.333333333333336</p>
Of course, now you have a ridiculously long decimal, which brings us to our next function.
Taming Decimals with math.Round
You’ve done the math correctly and gotten a float. Now you need to present it like a civilized human, not a calculator from the 1980s. Enter math.Round.
This function does exactly what it says: it rounds a number to the nearest integer. But its real power comes from its optional second parameter: precision. You can tell it how many decimal places to keep. Want to round our price from above to two decimal places? Easy.
{{ $price := div 100.0 3 }} {{/* $price is now ~33.333333... */}}
<p>Rounded price: ${{ math.Round $price }}</p> → <p>Rounded price: $33</p>
<p>Properly rounded price: ${{ math.Round 2 $price }}</p> → <p>Properly rounded price: $33.33</p>
{{/* You can chain this all in one line */}}
<p>Final price: ${{ math.Round 2 (div 100.0 3) }}</p> → <p>Final price: $33.33</p>
A crucial note here: math.Round uses standard rounding rules (>.5 goes up, <.5 goes down). If you need more control, like always rounding down for financial calculations, you’ll need to look into creating a custom function or using math.Floor with some cleverness, as Hugo doesn’t provide math.Ceil or math.Floor out of the box. It’s a curious omission.
Best Practices and Pitfalls
- Beware of Zeros: Never, ever use
divwith a dynamic value as the divisor without checking if it’s zero first.{{ div 5 0 }}will bring your site build to a screeching halt with a division-by-zero error. Use anifstatement to guard against this. It’s not a graceful failure; it’s a show-stopping crash. - Type Consistency is Key: Mixing strings and numbers can lead to unexpected results or errors. Hugo will try to convert them, but it’s not always smart about it. Use
intandfloatto cast your variables explicitly before doing math on them. It’s a few extra keystrokes that will save you hours of debugging. - Modulo for Alternating Classes: Don’t forget about
mod. It’s incredibly useful for things like zebra-striping tables or alternating CSS classes in a loop.{{ mod $index 2 }}is the classic way to check for odd/even rows.
{{/* Safe division */}}
{{ $divisor := 0 }}
{{ if $divisor }}
{{ div 100 $divisor }} {{/* This won't execute */}}
{{ else }}
<p>Sorry, can't divide by zero. Did you forget to configure a value?</p>
{{ end }}
{{/* Using mod in a range loop */}}
<ul>
{{ range $index, $element := .Pages }}
<li class="item-{{ $index }} {{ if (eq (mod $index 2) 0 ) }}even{{ else }}odd{{ end }}">
{{ .Title }}
</li>
{{ end }}
</ul>
In the end, Hugo’s math functions are simple, a little quirky, and absolutely essential. Master their idiosyncrasies—especially that argument order and integer division—and you’ll be calculating anything you need without breaking a sweat.