Skip to main content

Math functions — Marketing Cloud AMPscript reference

AMPscript's math surface — six functions, no operators, silent string-to-number coercion. Divide-by-zero behavior changes between tenants, money math has floating-point traps, and 'abc' becomes 0 without warning. The patterns that survive at scale.

Reference·Last updated 2026-05-13·Drafted by Lira · Edited by German Medina

AMPscript's math surface is the smallest in the catalog: six functions for arithmetic plus Round for precision control. There are no operators — no +, -, *, /. Every arithmetic operation is a function call: Add(@a, @b), Multiply(@price, @qty). Inputs are coerced from string to number at call time, silently — Add("three", 5) returns 5 instead of raising. The list below is the surface plus the failure modes you only find in production.

Official syntax

%%[
  /* Basic arithmetic — every operation is a function */
  SET @sum  = Add(@price, @shipping)
  SET @diff = Subtract(@total, @discount)
  SET @prod = Multiply(@unitPrice, @qty)
  SET @quot = Divide(@total, @taxRate)   /* see divide-by-zero trap below */

  /* Modulo — useful for alternating personalization */
  SET @bucket = Mod(@subscriberId, 4)
  /* 0, 1, 2, 3 — pick a content variant per recipient */

  /* Rounding */
  SET @rounded = Round(@price, 2)         /* 2 decimal places */
  SET @whole   = Round(@price)            /* nearest integer when arg omitted */

  /* Nested calls — readable for short expressions */
  SET @grandTotal = Add(Multiply(@price, @qty), @shipping)

  /* For longer expressions, build with named intermediate vars */
  SET @subtotal = Multiply(@price, @qty)
  SET @afterTax = Multiply(@subtotal, Add(1, @taxRate))
  SET @final    = Subtract(@afterTax, @discount)
]%%

The supported set:

| Function | What it does | Notes | |---|---|---| | Add(a, b) | a + b | Variadic in some tenants (Add(a, b, c)); stick to binary for portability | | Subtract(a, b) | a - b | Order matters; no unary negation function | | Multiply(a, b) | a * b | | | Divide(a, b) | a / b | Divide-by-zero behavior is tenant-dependent — guard explicitly | | Mod(a, b) | Remainder of a / b | Useful for "every Nth recipient" alternating content | | Round(n [, digits]) | Round to digits decimals | Omitting digits rounds to integer; rounding mode is banker's rounding (away from zero on .5) |

Reference:

What survives in production

String-to-number coercion is silent — bad input becomes 0

AMPscript coerces every math input from its string representation to a number at function-call time. If the input doesn't parse as a number, the function uses 0 and returns a wrong result without complaint.

%%[
  /* "3" + "4" → 7. Standard coercion. */
  SET @a = Add("3", "4")           /* → 7 */

  /* "abc" + 5 → 5. The "abc" coerced to 0, then 0 + 5 = 5. */
  SET @b = Add("abc", 5)           /* → 5 — silently wrong */

  /* "1,000" + 5 → 5. The comma breaks the parse. */
  SET @c = Add("1,000", 5)         /* → 5 — comma fails parse, becomes 0 */

  /* NULL + 5 → 5. NULL coerced to 0. */
  SET @d = Add(NULL, 5)            /* → 5 */
]%%

Defense: validate before math. If the input came from a Lookup or AttributeValue, check Empty() first and default. If the input has formatting (commas, currency symbols), strip with Replace before passing into a math function:

%%[
  SET @raw = AttributeValue("CustomerLifetimeValue")  /* may be "$1,234.50" */
  SET @clean = Replace(Replace(@raw, "$", ""), ",", "")
  SET @value = Add(@clean, 0)                         /* coerce to number */

  IF Empty(@raw) THEN
    SET @value = 0
  ENDIF
]%%

Divide-by-zero behavior is tenant-dependent — guard explicitly

Divide(a, 0) returns 0 on some tenants and raises a runtime error on others. The behavior changed across SFMC editions and isn't guaranteed forward.

%%[
  /* AT RISK — depending on tenant, this either returns 0 or errors */
  SET @rate = Divide(@conversions, @impressions)

  /* DURABLE — guard before dividing */
  IF @impressions == 0 OR Empty(@impressions) THEN
    SET @rate = 0
  ELSE
    SET @rate = Divide(@conversions, @impressions)
  ENDIF
]%%

The hand-off failure: an email tested in dev with @impressions = 1000 ships, runs against a recipient where @impressions = 0, and either renders 0 (silently wrong personalization) or fails the Send Activity step (loud but rare). Guard every Divide where the denominator could be zero, NULL, or non-numeric.

Floating-point precision: money math is unsafe at scale

AMPscript uses floating-point arithmetic (.NET doubles under the hood). The classic floating-point precision issues apply: Add(0.1, 0.2) may not equal 0.3 exactly, and currency calculations across many operations accumulate error.

%%[
  SET @cents = Multiply(@amount, 100)
  SET @rounded = Round(@cents, 0)              /* round to whole cents */
  SET @final = Divide(@rounded, 100)           /* back to dollars */
  /* Final is now precise to 2 decimal places — no floating-point tail */

  /* The naive approach loses precision over many operations */
  SET @bad = Add(Multiply(@price, 0.07), @price)
  /* For some prices, @bad has trailing digits like 19.450000000001 */
]%%

The Cleon rule: do money math in cents (integers), not dollars. Multiply by 100 at the start, work in cents, divide by 100 only at the display step. For high-precision contexts (tax math, multi-currency reconciliation), do the math in a SQL Activity that uses DECIMAL(18,4) and pass the result as a pre-computed column to AMPscript.

Mod for "every Nth recipient" personalization

Mod is the underused workhorse for alternating personalization without an upstream column. Pair it with a stable, integer-shaped per-recipient value (a SubscriberKey hash, an ID-mod-N pre-computed in SQL, etc.) and you can branch on 0/1/2 to vary the email body.

%%[
  SET @subId = AttributeValue("SubscriberKey")
  /* SubscriberKey is a string; coerce to number where possible.
     Better: pre-compute SubscriberKey % 4 into a column upstream. */
  SET @bucket = Mod(@subId, 4)

  IF @bucket == 0 THEN
    SET @heroImage = "hero-a.jpg"
  ELSEIF @bucket == 1 THEN
    SET @heroImage = "hero-b.jpg"
  ELSEIF @bucket == 2 THEN
    SET @heroImage = "hero-c.jpg"
  ELSE
    SET @heroImage = "hero-d.jpg"
  ENDIF
]%%

A SubscriberKey that's a UUID or non-numeric string coerces to 0 (see the silent-coercion trap), and every recipient lands in bucket 0. If your audience has non-numeric IDs, pre-compute the bucket in SQL upstream and read it as a column.

Round uses banker's rounding by default

Round(0.5) returns 0 in AMPscript on some tenants (banker's rounding rounds to even), 1 on others (half-up rounding). The difference is small per-row but compounds across an audience.

%%[
  SET @a = Round(0.5)    /* tenant-dependent: 0 or 1 */
  SET @b = Round(1.5)    /* tenant-dependent: 2 (both modes) */
  SET @c = Round(2.5)    /* tenant-dependent: 2 (banker's) or 3 (half-up) */
]%%

When the rounding behavior matters for legal / accounting correctness (tax-inclusive prices, percentages that have to sum to exactly 100%), don't round in AMPscript. Pre-compute in SQL where you can choose the rounding function explicitly (ROUND vs FLOOR vs CEILING).

Nested calls hurt readability past 3 levels

AMPscript's function-call syntax for math is workable for short expressions and painful for long ones. The break-even is around three nested calls — past that, named intermediate variables read better.

%%[
  /* OK — short enough */
  SET @x = Add(Multiply(@price, @qty), @shipping)

  /* PAINFUL — try parsing the order of operations in a glance */
  SET @y = Divide(Subtract(Multiply(@price, @qty), @discount), Add(1, @taxRate))

  /* BETTER — name the intermediates, comment the formula */
  /* y = (price * qty - discount) / (1 + taxRate) */
  SET @subtotal    = Multiply(@price, @qty)
  SET @afterDisc   = Subtract(@subtotal, @discount)
  SET @taxFactor   = Add(1, @taxRate)
  SET @y           = Divide(@afterDisc, @taxFactor)
]%%

The hand-off failure isn't a bug — it's that the next person changing the formula has to mentally parse the parentheses and order of operations. Six months later they introduce an arithmetic error because the original expression was unreadable.

Quick decision

Use Add / Subtract / Multiply / Divide when:

  • The arithmetic is short (1-3 operations) and the inputs are validated.

Use Mod when:

  • You need alternating content per-recipient and you have a stable numeric ID. Pre-compute the bucket upstream if IDs aren't numeric.

Use Round when:

  • Output goes to a subscriber and decimal precision matters for display. Confirm the rounding mode if accounting-grade precision is required.

Use named intermediate variables when:

  • The formula has 4+ operations or more than 2 levels of nesting. Readability prevents the next hand-off bug.

Do the math in SQL instead when:

  • Money or tax arithmetic where precision matters (cents-as-integer pattern in SQL is cleaner than the AMPscript workaround).
  • The same calculation runs for every recipient on a large send.
  • You need power, square-root, or any math function AMPscript doesn't ship.
  • Rounding mode matters and you need explicit control (FLOOR, CEILING, banker's vs half-up).

Related

More AMPscript reference pages incoming: Validation · Data Extension · Subscriber/Profile · Cloud-write · Encoding/Hashing · Style Guide.