Groovy Number Math – Cookbook Guide with 10+ Examples

Groovy number math handling well beyond what Java offers. See 13 tested examples covering BigDecimal defaults, times/upto/downto loops, power operator, integer division, type coercion, abs/round/ceil/floor, currency formatting, random numbers, and numeric ranges.

“In most languages, 0.1 + 0.2 equals 0.30000000000000004. In Groovy, it equals 0.3 – because Groovy chose BigDecimal as the default and saved us all from floating-point therapy.”

Donald Knuth, The Art of Computer Programming

Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner | Reading Time: 18 minutes

Write 3.14 in Java and you get a double – a floating-point type that can’t represent most decimals exactly. Write 3.14 in Groovy and you get a BigDecimal. That single difference in the groovy number math system – detailed in the official Groovy numbers documentation – eliminates an entire class of bugs around financial calculations, rounding errors, and precision loss.

But Groovy’s number enhancements go far beyond BigDecimal. The GDK adds methods like times(), upto(), and downto() directly to integers, making numeric iteration read like English. The power operator ** replaces Math.pow(), intdiv() gives you integer division without casting, and numeric ranges let you express sequences like 1..10 as first-class objects. These 13 examples will show you every number trick Groovy has.

Financial math, loops, currency formatting, random data generation – Groovy makes all of these cleaner and safer than raw Java. Here are the details.

Quick Reference Table

FeatureSyntaxExampleResult
BigDecimal literal3.143.14.classjava.math.BigDecimal
Power operator**2 ** 101024
Integer divisionintdiv()7.intdiv(2)3
Times loopN.times { }3.times { print it }012
Upto loopa.upto(b) { }1.upto(3) { print it }123
Downto loopa.downto(b) { }3.downto(1) { print it }321
Absolute valueabs()(-5).abs()5
Roundround(n)3.456.round(2)3.46
Numeric rangea..b(1..5).toList()[1, 2, 3, 4, 5]
Type coercionautomatic(2 + 3.0).classjava.math.BigDecimal

Examples

Example 1: BigDecimal as the Default Decimal Type

What we’re doing: Demonstrating that Groovy uses BigDecimal for decimal literals by default, avoiding floating-point precision issues.

Example 1: BigDecimal Defaults

// Groovy decimal literals are BigDecimal by default
def price = 19.99
def tax = 0.1
def discount = 0.2

println "price type:    ${price.class.simpleName}"
println "tax type:      ${tax.class.simpleName}"
println "discount type: ${discount.class.simpleName}"

// The famous floating-point trap - not a problem in Groovy!
println "\n--- Precision Test ---"
println "0.1 + 0.2 = ${0.1 + 0.2}"
println "0.1 + 0.2 == 0.3? ${0.1 + 0.2 == 0.3}"

// Compare with explicit double (Java-style)
double d1 = 0.1
double d2 = 0.2
println "\nWith doubles: ${d1 + d2}"
println "With doubles == 0.3? ${d1 + d2 == 0.3}"

// Integer literals are Integer (or Long for large values)
def count = 42
def big = 9_999_999_999
println "\n42 type: ${count.class.simpleName}"
println "9999999999 type: ${big.class.simpleName}"

// Underscore separators for readability
def million = 1_000_000
def hex = 0xFF_EC_DE
def binary = 0b1010_1100
println "\nMillion: ${million}"
println "Hex: ${hex}"
println "Binary: ${binary}"

Output

price type:    BigDecimal
tax type:      BigDecimal
discount type: BigDecimal

--- Precision Test ---
0.1 + 0.2 = 0.3
0.1 + 0.2 == 0.3? true

With doubles: 0.30000000000000004
With doubles == 0.3? false

42 type: Integer
9999999999 type: Long

Million: 1000000
Hex: 16772318
Binary: 172

What happened here: Every decimal literal in Groovy (19.99, 0.1, 0.2) creates a BigDecimal, not a double. This means 0.1 + 0.2 genuinely equals 0.3 – no floating-point surprises. When you explicitly declare a double with the Java type, you get the classic precision issue. For integers, Groovy uses Integer for values that fit in 32 bits and automatically promotes to Long for larger values. Underscore separators (1_000_000) work just like in Java for readability – the compiler strips them out.

Example 2: times(), upto(), and downto() Loops

What we’re doing: Using Groovy’s GDK methods on integers for clean, readable iteration.

Example 2: Numeric Loop Methods

// times() - repeat N times (index 0 to N-1)
print "times(5): "
5.times { print "${it} " }
println()

// times() without using the index
print "Stars: "
3.times { print "* " }
println()

// upto() - count from A up to B (inclusive)
print "upto(1,5): "
1.upto(5) { print "${it} " }
println()

// downto() - count from A down to B (inclusive)
print "downto(10,6): "
10.downto(6) { print "${it} " }
println()

// Practical: build a multiplication table row
println "\n--- 7x Table ---"
1.upto(10) { n ->
    println "7 x ${n.toString().padLeft(2)} = ${(7 * n).toString().padLeft(2)}"
}

// Practical: countdown timer simulation
println "\n--- Countdown ---"
3.downto(1) { println "T-${it}..." }
println "Liftoff!"

// Chaining with collect-style behavior
def squares = []
1.upto(5) { squares << it * it }
println "\nSquares 1-5: ${squares}"

Output

times(5): 0 1 2 3 4
Stars: * * *
upto(1,5): 1 2 3 4 5
downto(10,6): 10 9 8 7 6

--- 7x Table ---
7 x  1 =  7
7 x  2 = 14
7 x  3 = 21
7 x  4 = 28
7 x  5 = 35
7 x  6 = 42
7 x  7 = 49
7 x  8 = 56
7 x  9 = 63
7 x 10 = 70

--- Countdown ---
T-3...
T-2...
T-1...
Liftoff!

Squares 1-5: [1, 4, 9, 16, 25]

What happened here: The GDK adds times(), upto(), and downto() directly to Number. times() runs the closure N times with an index starting at 0. upto() and downto() iterate inclusively between two bounds. These replace many common for loops with more expressive code. The closure parameter it receives the current number. You can name it explicitly (like n in the multiplication table) for clarity. These methods work on all integer types – Integer, Long, and even BigInteger.

Example 3: Power Operator (**)

What we’re doing: Using Groovy’s ** operator for exponentiation instead of Math.pow().

Example 3: Power Operator

// Basic exponentiation
println "2 ** 10 = ${2 ** 10}"
println "3 ** 4  = ${3 ** 4}"
println "10 ** 6 = ${10 ** 6}"

// Works with BigDecimal too
println "\n1.5 ** 3 = ${1.5 ** 3}"
println "2.5 ** 2 = ${2.5 ** 2}"

// Negative exponents
println "\n2 ** -1 = ${2 ** -1}"
println "2 ** -3 = ${2 ** -3}"

// Type of result depends on operands
println "\nTypes:"
println "2 ** 10 type:   ${(2 ** 10).class.simpleName}"     // Integer
println "2 ** 32 type:   ${(2 ** 32).class.simpleName}"     // Long promotion
println "2 ** 100 type:  ${(2 ** 100).class.simpleName}"    // BigInteger for huge results
println "1.5 ** 2 type:  ${(1.5 ** 2).class.simpleName}"   // BigDecimal

// Practical: compound interest calculation
def principal = 10_000.00
def rate = 0.05
def years = 10
def finalAmount = principal * (1 + rate) ** years
println "\n--- Compound Interest ---"
println "Principal: \$${principal}"
println "Rate: ${rate * 100}%"
println "Years: ${years}"
println "Final amount: \$${finalAmount.setScale(2, BigDecimal.ROUND_HALF_UP)}"

Output

2 ** 10 = 1024
3 ** 4  = 81
10 ** 6 = 1000000

1.5 ** 3 = 3.375
2.5 ** 2 = 6.25

2 ** -1 = 0.5
2 ** -3 = 0.125

Types:
2 ** 10 type:   Integer
2 ** 32 type:   Long
2 ** 100 type:  BigInteger
1.5 ** 2 type:  BigDecimal

--- Compound Interest ---
Principal: $10000.00
Rate: 5.0%
Years: 10
Final amount: $16288.95

What happened here: The ** operator is Groovy’s native power operator – no need for Math.pow() which returns a double. Groovy’s version is smarter about types: integer bases with positive exponents stay as integers (promoting to Long or BigInteger as needed), and BigDecimal bases stay as BigDecimal. Negative exponents produce decimal results. The compound interest calculation shows why this matters – BigDecimal arithmetic gives you exact results for financial math without any floating-point drift.

Example 4: Integer Division with intdiv()

What we’re doing: Using intdiv() for integer division and understanding Groovy’s division behavior.

Example 4: Integer Division

// Groovy's / operator returns BigDecimal for integer division!
println "--- Default Division ---"
println "7 / 2 = ${7 / 2}"
println "7 / 2 type: ${(7 / 2).class.simpleName}"
println "10 / 3 = ${10 / 3}"

// intdiv() gives you truncated integer division
println "\n--- intdiv() ---"
println "7.intdiv(2) = ${7.intdiv(2)}"
println "7.intdiv(2) type: ${7.intdiv(2).class.simpleName}"
println "10.intdiv(3) = ${10.intdiv(3)}"
println "-7.intdiv(2) = ${(-7).intdiv(2)}"

// Modulo still works as expected
println "\n--- Modulo (%) ---"
println "7 % 2 = ${7 % 2}"
println "10 % 3 = ${10 % 3}"
println "-7 % 2 = ${-7 % 2}"

// Practical: pagination calculation
def totalItems = 47
def pageSize = 10
def totalPages = totalItems.intdiv(pageSize) + (totalItems % pageSize ? 1 : 0)
println "\n--- Pagination ---"
println "Total items: ${totalItems}"
println "Page size: ${pageSize}"
println "Total pages: ${totalPages}"

1.upto(totalPages) { page ->
    def start = (page - 1) * pageSize
    def end = Math.min(start + pageSize - 1, totalItems - 1)
    println "Page ${page}: items ${start}-${end}"
}

// Practical: time conversion
def totalSeconds = 7384
def hours = totalSeconds.intdiv(3600)
def minutes = (totalSeconds % 3600).intdiv(60)
def seconds = totalSeconds % 60
println "\n${totalSeconds} seconds = ${hours}h ${minutes}m ${seconds}s"

Output

--- Default Division ---
7 / 2 = 3.5
7 / 2 type: BigDecimal
10 / 3 = 3.3333333333

--- intdiv() ---
7.intdiv(2) = 3
7.intdiv(2) type: Integer
10.intdiv(3) = 3
-7.intdiv(2) = -3

--- Modulo (%) ---
7 % 2 = 1
10 % 3 = 1
-7 % 2 = -1

--- Pagination ---
Total items: 47
Page size: 10
Total pages: 5
Page 1: items 0-9
Page 2: items 10-19
Page 3: items 20-29
Page 4: items 30-39
Page 5: items 40-46

7384 seconds = 2h 3m 4s

What happened here: This is a major Groovy gotcha for Java developers: the / operator on two integers returns a BigDecimal, not an integer. So 7 / 2 is 3.5, not 3. When you need truncated integer division (like Java’s / on ints), use intdiv(). This is a deliberate design choice – Groovy prefers mathematical correctness over performance. The modulo operator % works the same as Java. The pagination and time conversion examples show where intdiv() is essential for real-world calculations.

Example 5: Type Coercion Rules

What we’re doing: Understanding how Groovy automatically promotes number types when mixing them in arithmetic.

Example 5: Type Coercion

// Groovy widens to the "larger" type in mixed arithmetic
println "--- Type Promotion Rules ---"

// int + int = int
def r1 = 2 + 3
println "int + int = ${r1} (${r1.class.simpleName})"

// int + long = long
def r2 = 2 + 3L
println "int + long = ${r2} (${r2.class.simpleName})"

// int + BigDecimal = BigDecimal
def r3 = 2 + 3.0
println "int + BigDecimal = ${r3} (${r3.class.simpleName})"

// int + double = double
def r4 = 2 + 3.0d
println "int + double = ${r4} (${r4.class.simpleName})"

// int + float = double (Groovy promotes floats to double)
def r5 = 2 + 3.0f
println "int + float = ${r5} (${r5.class.simpleName})"

// BigDecimal + double = double (double wins!)
def r6 = 2.0 + 3.0d
println "BigDecimal + double = ${r6} (${r6.class.simpleName})"

println "\n--- Explicit Type Suffixes ---"
println "42     -> ${(42).class.simpleName}"
println "42L    -> ${(42L).class.simpleName}"
println "42G    -> ${(42G).class.simpleName}"
println "3.14   -> ${(3.14).class.simpleName}"
println "3.14d  -> ${(3.14d).class.simpleName}"
println "3.14f  -> ${(3.14f).class.simpleName}"
println "3.14G  -> ${(3.14G).class.simpleName}"

println "\n--- Casting ---"
def bigVal = 3.14159
println "BigDecimal: ${bigVal}"
println "as int:     ${bigVal as int}"
println "as double:  ${bigVal as double}"
println "toInteger:  ${bigVal.toInteger()}"
println "toDouble:   ${bigVal.toDouble()}"
println "intValue:   ${bigVal.intValue()}"

Output

--- Type Promotion Rules ---
int + int = 5 (Integer)
int + long = 5 (Long)
int + BigDecimal = 5.0 (BigDecimal)
int + double = 5.0 (Double)
int + float = 5.0 (Double)
BigDecimal + double = 5.0 (Double)

--- Explicit Type Suffixes ---
42     -> Integer
42L    -> Long
42G    -> BigInteger
3.14   -> BigDecimal
3.14d  -> Double
3.14f  -> Float
3.14G  -> BigDecimal

--- Casting ---
BigDecimal: 3.14159
as int:     3
as double:  3.14159
toInteger:  3
toDouble:   3.14159
intValue:   3

What happened here: Groovy follows a promotion hierarchy: Integer < Long < BigInteger < BigDecimal < Double. When you mix types, the result is the “wider” type. One critical detail: mixing BigDecimal with double produces a double, which means you lose BigDecimal’s precision. This is a common source of bugs – if you’re doing financial math, keep everything as BigDecimal and avoid double entirely. The G suffix creates BigInteger for integer literals and BigDecimal for decimal literals. The as keyword and toXxx() methods handle explicit conversions.

Example 6: GDK Number Methods – abs, round, ceil, floor

What we’re doing: Using Groovy’s built-in number methods for common math operations.

Example 6: abs, round, ceil, floor

// abs() - absolute value
println "--- abs() ---"
println "(-42).abs() = ${(-42).abs()}"
println "(-3.14).abs() = ${(-3.14).abs()}"
println "42.abs() = ${42.abs()}"

// round() - round BigDecimal to N decimal places
println "\n--- round() ---"
def pi = 3.14159265
println "pi.round(0) = ${pi.round(0)}"
println "pi.round(2) = ${pi.round(2)}"
println "pi.round(4) = ${pi.round(4)}"

// Rounding with different values
println "2.345.round(2) = ${2.345.round(2)}"
println "2.355.round(2) = ${2.355.round(2)}"
println "9.995.round(2) = ${9.995.round(2)}"

// Math.ceil and Math.floor (return double)
println "\n--- ceil() and floor() ---"
println "Math.ceil(3.2) = ${Math.ceil(3.2)}"
println "Math.ceil(3.8) = ${Math.ceil(3.8)}"
println "Math.ceil(-3.2) = ${Math.ceil(-3.2)}"
println "Math.floor(3.8) = ${Math.floor(3.8)}"
println "Math.floor(3.2) = ${Math.floor(3.2)}"
println "Math.floor(-3.2) = ${Math.floor(-3.2)}"

// min() and max()
println "\n--- min() and max() ---"
def numbers = [45, 12, 89, 3, 67, 23]
println "List: ${numbers}"
println "min: ${numbers.min()}"
println "max: ${numbers.max()}"
println "Math.min(5, 3): ${Math.min(5, 3)}"
println "Math.max(5, 3): ${Math.max(5, 3)}"

// Practical: clamp a value to a range
def clamp = { val, low, high -> Math.max(low, Math.min(high, val)) }
println "\nClamp 150 to [0,100]: ${clamp(150, 0, 100)}"
println "Clamp -20 to [0,100]: ${clamp(-20, 0, 100)}"
println "Clamp 50 to [0,100]:  ${clamp(50, 0, 100)}"

Output

--- abs() ---
(-42).abs() = 42
(-3.14).abs() = 3.14
42.abs() = 42

--- round() ---
pi.round(0) = 3
pi.round(2) = 3.14
pi.round(4) = 3.1416

2.345.round(2) = 2.35
2.355.round(2) = 2.36
9.995.round(2) = 10.00

--- ceil() and floor() ---
Math.ceil(3.2) = 4.0
Math.ceil(3.8) = 4.0
Math.ceil(-3.2) = -3.0
Math.floor(3.8) = 3.0
Math.floor(3.2) = 3.0
Math.floor(-3.2) = -4.0

--- min() and max() ---
List: [45, 12, 89, 3, 67, 23]
min: 3
max: 89
Math.min(5, 3): 3
Math.max(5, 3): 5

Clamp 150 to [0,100]: 100
Clamp -20 to [0,100]: 0
Clamp 50 to [0,100]:  50

What happened here: Groovy adds abs() directly to number types so you can call it as a method instead of Math.abs(). The round() method on BigDecimal rounds to the specified number of decimal places using the default rounding mode (HALF_UP). Math.ceil() and Math.floor() come from Java’s Math class and return double. For lists, Groovy adds min() and max() methods directly. The clamp pattern – constraining a value to a range – combines Math.min and Math.max and is useful for validation, UI bounds, and game logic.

Example 7: Numeric Ranges

What we’re doing: Using Groovy’s range operator (..) to create numeric sequences and iterate over them.

Example 7: Numeric Ranges

// Inclusive range (both ends included)
def inclusive = 1..5
println "1..5 = ${inclusive.toList()}"
println "Type: ${inclusive.class.simpleName}"

// Exclusive range (end excluded)
def exclusive = 1..<5
println "1..<5 = ${exclusive.toList()}"

// Reverse range
def reversed = 5..1
println "5..1 = ${reversed.toList()}"

// Range properties
def r = 1..10
println "\nRange 1..10:"
println "  size: ${r.size()}"
println "  from: ${r.from}"
println "  to:   ${r.to}"
println "  contains 5: ${5 in r}"
println "  contains 11: ${11 in r}"

// Using ranges with step (via collect or step method)
println "\nEven numbers 2-20:"
def evens = (2..20).step(2)
println evens

println "\nCountdown by 3:"
def countdown = (15..0).step(3)
println countdown

// Ranges in switch statements
def score = 85
def grade = switch (score) {
    case 90..100 -> 'A'
    case 80..<90 -> 'B'
    case 70..<80 -> 'C'
    case 60..<70 -> 'D'
    default      -> 'F'
}
println "\nScore ${score} = Grade ${grade}"

// Range with collect for transformation
def squares = (1..6).collect { it ** 2 }
println "\nSquares: ${squares}"

// Range for list slicing
def alphabet = ('a'..'z').toList()
println "First 5 letters: ${alphabet[0..4]}"
println "Last 3 letters: ${alphabet[-3..-1]}"

Output

1..5 = [1, 2, 3, 4, 5]
Type: IntRange
1..<5 = [1, 2, 3, 4]
5..1 = [5, 4, 3, 2, 1]

Range 1..10:
  size: 10
  from: 1
  to:   10
  contains 5: true
  contains 11: false

Even numbers 2-20:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Countdown by 3:
[15, 12, 9, 6, 3, 0]

Score 85 = Grade B

Squares: [1, 4, 9, 16, 25, 36]

First 5 letters: [a, b, c, d, e]
Last 3 letters: [x, y, z]

What happened here: Ranges are first-class objects in Groovy (type IntRange for integers). The .. operator creates an inclusive range, while ..< excludes the upper bound. Ranges support contains (via the in keyword), step() for intervals, iteration, and they work beautifully in switch statements for value classification. The collect method transforms each element, and ranges can slice lists and strings using bracket notation. Ranges are lazy – they don’t create an array of all values in memory until you call toList(). For more on ranges with lists, see our Groovy Lists guide.

Example 8: Random Numbers

What we’re doing: Generating random numbers using multiple approaches – Math.random(), Random, and Groovy’s collection methods.

Example 8: Random Numbers

// Math.random() - returns double between 0.0 (inclusive) and 1.0 (exclusive)
println "Math.random(): ${Math.random().round(4)}"

// java.util.Random - more control
def rng = new Random()
println "nextInt():     ${rng.nextInt()}"
println "nextInt(100):  ${rng.nextInt(100)}"        // 0 to 99
println "nextDouble():  ${rng.nextDouble().round(4)}"
println "nextBoolean(): ${rng.nextBoolean()}"

// Seeded random for reproducible results
def seeded = new Random(42)
def sequence = (1..5).collect { seeded.nextInt(100) }
println "\nSeeded sequence: ${sequence}"

// Re-create same seed - same sequence
def seeded2 = new Random(42)
def sequence2 = (1..5).collect { seeded2.nextInt(100) }
println "Same seed again: ${sequence2}"
println "Identical? ${sequence == sequence2}"

// Random integer in a range
def randomInRange = { int min, int max ->
    new Random().nextInt(max - min + 1) + min
}
println "\nRandom 1-6 (dice): ${randomInRange(1, 6)}"
println "Random 10-20: ${randomInRange(10, 20)}"

// Shuffle a list
def deck = ('A'..'K').toList()
Collections.shuffle(deck)
println "\nShuffled: ${deck.take(5)}..."

// Pick random elements from a list
def colors = ['Red', 'Blue', 'Green', 'Yellow', 'Purple']
println "Random color: ${colors[rng.nextInt(colors.size())]}"

// Generate random data
println "\n--- Random Test Data ---"
def names = ['Nirranjan', 'Viraj', 'Prathamesh', 'Prathamesh', 'Viraj']
def cities = ['London', 'Paris', 'Tokyo', 'Sydney', 'Berlin']
3.times {
    println "${names[rng.nextInt(names.size())].padRight(10)} | Age: ${randomInRange(20, 60)} | ${cities[rng.nextInt(cities.size())]}"
}

Output

Math.random(): 0.7382
nextInt():     -1289473847
nextInt(100):  47
nextDouble():  0.8294
nextBoolean(): true

Seeded sequence: [0, 68, 54, 43, 52]
Same seed again: [0, 68, 54, 43, 52]
Identical? true

Random 1-6 (dice): 4
Random 10-20: 15

Shuffled: [G, B, K, D, A]...

Random color: Green

--- Random Test Data ---
Prathamesh    | Age: 34 | Tokyo
Nirranjan      | Age: 52 | Paris
Viraj        | Age: 28 | London

What happened here: Groovy doesn’t add special random methods to numbers, but Java’s Random class works perfectly with Groovy syntax. The key techniques: nextInt(bound) gives you 0 to bound-1, and nextInt(max - min + 1) + min gives you a range. Seeded randoms with new Random(42) produce the same sequence every time – essential for tests. Collections.shuffle() randomizes a list in place. The test data generator shows a practical pattern: combine random selection from lists with random numbers to create realistic mock data for testing.

Example 9: Currency Formatting

What we’re doing: Formatting numbers as currency using NumberFormat and DecimalFormat.

Example 9: Currency Formatting

import java.text.NumberFormat
import java.text.DecimalFormat

// Default locale currency formatting
def amount = 1234567.89

def usFmt = NumberFormat.getCurrencyInstance(Locale.US)
def ukFmt = NumberFormat.getCurrencyInstance(Locale.UK)
def deFmt = NumberFormat.getCurrencyInstance(Locale.GERMANY)
def jpFmt = NumberFormat.getCurrencyInstance(Locale.JAPAN)

println "--- Currency Formatting ---"
println "US:      ${usFmt.format(amount)}"
println "UK:      ${ukFmt.format(amount)}"
println "Germany: ${deFmt.format(amount)}"
println "Japan:   ${jpFmt.format(amount)}"

// Custom decimal formatting
def df = new DecimalFormat('#,##0.00')
println "\nCustom:  ${df.format(amount)}"

def df2 = new DecimalFormat('$#,##0.00')
println "With \$:  ${df2.format(amount)}"

// Percentage formatting
def pctFmt = NumberFormat.getPercentInstance()
pctFmt.minimumFractionDigits = 1
println "\n--- Percentages ---"
println "0.856 = ${pctFmt.format(0.856)}"
println "0.5   = ${pctFmt.format(0.5)}"
println "1.0   = ${pctFmt.format(1.0)}"

// Practical: receipt formatting
println "\n--- Receipt ---"
def items = [
    [name: 'Widget A', qty: 3, price: 29.99],
    [name: 'Widget B', qty: 1, price: 149.50],
    [name: 'Service Fee', qty: 1, price: 15.00]
]

def formatter = NumberFormat.getCurrencyInstance(Locale.US)
def subtotal = 0.0

items.each { item ->
    def lineTotal = item.price * item.qty
    subtotal += lineTotal
    println "${item.name.padRight(15)} x${item.qty}  ${formatter.format(lineTotal).padLeft(10)}"
}

def taxRate = 0.08
def tax = (subtotal * taxRate).setScale(2, BigDecimal.ROUND_HALF_UP)
def total = subtotal + tax

println "${'-' * 35}"
println "${'Subtotal'.padRight(20)} ${formatter.format(subtotal).padLeft(10)}"
println "${'Tax (8%)'.padRight(20)} ${formatter.format(tax).padLeft(10)}"
println "${'TOTAL'.padRight(20)} ${formatter.format(total).padLeft(10)}"

Output

--- Currency Formatting ---
US:      $1,234,567.89
UK:      £1,234,567.89
Germany: 1.234.567,89 €
Japan:   ¥1,234,568

Custom:  1,234,567.89
With $:  $1,234,567.89

--- Percentages ---
0.856 = 85.6%
0.5   = 50.0%
1.0   = 100.0%

--- Receipt ---
Widget A        x3     $89.97
Widget B        x1    $149.50
Service Fee     x1     $15.00
-----------------------------------
Subtotal                $254.47
Tax (8%)                 $20.36
TOTAL                  $274.83

What happened here: Java’s NumberFormat handles locale-aware currency formatting – notice how Germany uses dots for thousands and commas for decimals, the opposite of the US. DecimalFormat gives you custom patterns. The receipt example shows a real-world pattern: use BigDecimal for all money calculations (which Groovy does by default), setScale() to control rounding, and NumberFormat for display. The padRight() and padLeft() methods from Groovy’s GDK handle column alignment. Always store and compute money as BigDecimal – only format it for display.

Example 10: BigDecimal Precision Control

What we’re doing: Controlling scale and rounding modes on BigDecimal for precise financial calculations.

Example 10: BigDecimal Precision

import java.math.RoundingMode

// setScale - set number of decimal places with rounding mode
def val = 3.14159

println "--- setScale() ---"
println "HALF_UP:   ${val.setScale(2, RoundingMode.HALF_UP)}"
println "HALF_DOWN: ${val.setScale(2, RoundingMode.HALF_DOWN)}"
println "CEILING:   ${val.setScale(2, RoundingMode.CEILING)}"
println "FLOOR:     ${val.setScale(2, RoundingMode.FLOOR)}"
println "UP:        ${val.setScale(2, RoundingMode.UP)}"
println "DOWN:      ${val.setScale(2, RoundingMode.DOWN)}"

// The 0.5 rounding edge case
println "\n--- Rounding 2.5 ---"
def half = 2.5
println "HALF_UP:   ${half.setScale(0, RoundingMode.HALF_UP)}"     // 3
println "HALF_DOWN: ${half.setScale(0, RoundingMode.HALF_DOWN)}"   // 2
println "HALF_EVEN: ${half.setScale(0, RoundingMode.HALF_EVEN)}"   // 2 (banker's rounding)

def half2 = 3.5
println "3.5 HALF_EVEN: ${half2.setScale(0, RoundingMode.HALF_EVEN)}"  // 4

// Division with scale control
println "\n--- Division Precision ---"
def a = 10.0
def b = 3.0
println "10 / 3 default:  ${a / b}"
println "10 / 3 scale(2): ${a.divide(b, 2, RoundingMode.HALF_UP)}"
println "10 / 3 scale(6): ${a.divide(b, 6, RoundingMode.HALF_UP)}"
println "10 / 3 scale(10): ${a.divide(b, 10, RoundingMode.HALF_UP)}"

// Practical: split a bill evenly
def billTotal = 100.00
def people = 3
def perPerson = billTotal.divide(people, 2, RoundingMode.HALF_UP)
def remainder = billTotal - (perPerson * people)

println "\n--- Bill Split ---"
println "Total: \$${billTotal}"
println "People: ${people}"
println "Each pays: \$${perPerson}"
println "Remainder: \$${remainder}"
println "First person pays: \$${perPerson + remainder} (covers the penny)"

Output

--- setScale() ---
HALF_UP:   3.14
HALF_DOWN: 3.14
CEILING:   3.15
FLOOR:     3.14
UP:        3.15
DOWN:      3.14

--- Rounding 2.5 ---
HALF_UP:   3
HALF_DOWN: 2
HALF_EVEN: 2
3.5 HALF_EVEN: 4

--- Division Precision ---
10 / 3 default:  3.3333333333
10 / 3 scale(2): 3.33
10 / 3 scale(6): 3.333333
10 / 3 scale(10): 3.3333333333

--- Bill Split ---
Total: $100.00
People: 3
Each pays: $33.33
Remainder: $0.01
First person pays: $33.34 (covers the penny)

What happened here: setScale() is the BigDecimal method you’ll use most – it sets the number of decimal places and the rounding mode. HALF_UP is what most people expect (round 0.5 up). HALF_EVEN (banker’s rounding) rounds to the nearest even number at 0.5 – this reduces cumulative rounding bias across many calculations. The division example shows that Groovy’s default / operator uses a generous scale, but divide() with explicit scale gives you precise control. The bill-splitting example demonstrates a common real-world problem: when dividing money, the pieces don’t always add up – you need to handle the remainder explicitly.

Example 11: Math Functions and Constants

What we’re doing: Using Java’s Math class for trigonometry, logarithms, and mathematical constants from Groovy.

Example 11: Math Functions

// Mathematical constants
println "--- Constants ---"
println "PI:  ${Math.PI}"
println "E:   ${Math.E}"

// Trigonometry (input in radians)
println "\n--- Trigonometry ---"
def angle = Math.PI / 4  // 45 degrees
println "sin(45°): ${Math.sin(angle).round(4)}"
println "cos(45°): ${Math.cos(angle).round(4)}"
println "tan(45°): ${Math.tan(angle).round(4)}"

// Convert between degrees and radians
println "90° in radians: ${Math.toRadians(90).round(4)}"
println "PI radians in degrees: ${Math.toDegrees(Math.PI).round(1)}"

// Logarithms
println "\n--- Logarithms ---"
println "ln(E):     ${Math.log(Math.E).round(4)}"
println "log10(100): ${Math.log10(100).round(4)}"
println "log2(256):  ${(Math.log(256) / Math.log(2)).round(4)}"

// Square root and cube root
println "\n--- Roots ---"
println "sqrt(144): ${Math.sqrt(144)}"
println "cbrt(27):  ${Math.cbrt(27)}"
println "sqrt(2):   ${Math.sqrt(2).round(6)}"

// Practical: distance between two points
def distance = { x1, y1, x2, y2 ->
    Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
}
println "\n--- Distance Calculation ---"
println "Distance (0,0) to (3,4): ${distance(0, 0, 3, 4)}"
println "Distance (1,2) to (4,6): ${distance(1, 2, 4, 6).round(4)}"

// Practical: convert temperature
def celsiusToFahrenheit = { c -> (c * 9 / 5) + 32 }
def fahrenheitToCelsius = { f -> (f - 32) * 5 / 9 }

println "\n--- Temperature ---"
[0, 20, 37, 100].each { c ->
    println "${c}°C = ${celsiusToFahrenheit(c).setScale(1, java.math.RoundingMode.HALF_UP)}°F"
}

Output

--- Constants ---
PI:  3.141592653589793
E:   2.718281828459045

--- Trigonometry ---
sin(45°): 0.7071
cos(45°): 0.7071
tan(45°): 1.0

90° in radians: 1.5708
PI radians in degrees: 180.0

--- Logarithms ---
ln(E):     1.0
log10(100): 2.0
log2(256):  8.0

--- Roots ---
sqrt(144): 12.0
cbrt(27):  3.0
sqrt(2):   1.414214

--- Distance Calculation ---
Distance (0,0) to (3,4): 5.0
Distance (1,2) to (4,6): 5.0

--- Temperature ---
0°C = 32.0°F
20°C = 68.0°F
37°C = 98.6°F
100°C = 212.0°F

What happened here: Java’s Math class is fully accessible from Groovy and provides all standard mathematical functions. Groovy’s round() method on Double makes formatting results much cleaner than in Java. The ** power operator works naturally in formulas like the distance calculation – compare (x2 - x1) ** 2 with Java’s verbose Math.pow(x2 - x1, 2). The temperature converter demonstrates how Groovy’s BigDecimal arithmetic gives exact results for the classic 37°C = 98.6°F conversion – try that with floating-point and you’ll get 98.60000000000001.

Example 12: Number Parsing and Conversion

What we’re doing: Converting between strings and numbers, handling different formats, and dealing with parsing errors.

Example 12: Number Parsing

// Groovy's toInteger(), toLong(), toDouble(), toBigDecimal()
println "--- String to Number ---"
println "'42'.toInteger():       ${'42'.toInteger()}"
println "'3.14'.toDouble():      ${'3.14'.toDouble()}"
println "'3.14'.toBigDecimal():  ${'3.14'.toBigDecimal()}"
println "'999999999999'.toLong(): ${'999999999999'.toLong()}"

// isNumber() and isInteger() checks
println "\n--- Validation ---"
['42', '3.14', 'abc', '', '0xFF', '1e10'].each { s ->
    println "'${s}'.isNumber():  ${"${s}".isNumber().toString().padRight(5)}  isInteger(): ${"${s}".isInteger()}"
}

// Safe parsing with try-catch
def safeParse(String s) {
    try {
        return s.toBigDecimal()
    } catch (NumberFormatException e) {
        return null
    }
}

println "\n--- Safe Parsing ---"
['100', '3.14', 'not-a-number', '1e5'].each { s ->
    def result = safeParse(s)
    println "safeParse('${s}'): ${result ?: 'INVALID'}"
}

// Integer base conversions
println "\n--- Base Conversions ---"
def num = 255
println "255 in binary:  ${Integer.toBinaryString(num)}"
println "255 in octal:   ${Integer.toOctalString(num)}"
println "255 in hex:     ${Integer.toHexString(num)}"
println "Parse binary '11111111': ${Integer.parseInt('11111111', 2)}"
println "Parse hex 'FF': ${Integer.parseInt('FF', 16)}"

// Number to String formatting
println "\n--- Formatting ---"
def n = 42.5
println "toString:  ${n.toString()}"
println "sprintf:   ${sprintf('%.4f', n as double)}"
println "sprintf:   ${sprintf('%010.2f', n as double)}"
println "sprintf:   ${sprintf('%+.1f', n as double)}"

Output

--- String to Number ---
'42'.toInteger():       42
'3.14'.toDouble():      3.14
'3.14'.toBigDecimal():  3.14
'999999999999'.toLong(): 999999999999

--- Validation ---
'42'.isNumber():  true   isInteger(): true
'3.14'.isNumber():  true   isInteger(): false
'abc'.isNumber():  false  isInteger(): false
''.isNumber():  false  isInteger(): false
'0xFF'.isNumber():  true   isInteger(): false
'1e10'.isNumber():  true   isInteger(): false

--- Safe Parsing ---
safeParse('100'): 100
safeParse('3.14'): 3.14
safeParse('not-a-number'): INVALID
safeParse('1e5'): 1E+5

--- Base Conversions ---
255 in binary:  11111111
255 in octal:   377
255 in hex:     ff
Parse binary '11111111': 255
Parse hex 'FF': 255

--- Formatting ---
toString:  42.5
sprintf:   42.5000
sprintf:   0000042.50
sprintf:   +42.5

What happened here: Groovy adds toInteger(), toDouble(), toBigDecimal(), and similar conversion methods directly to String. It also adds isNumber() and isInteger() for validation before parsing – always validate before converting to avoid NumberFormatException. For base conversions, Java’s Integer.toBinaryString() and Integer.parseInt(s, base) work smoothly. The sprintf() function provides C-style format strings for precise control over padding, decimal places, and sign display. For more on Groovy’s string methods, see our Groovy Strings guide.

Example 13: Practical – Statistics Calculator

What we’re doing: Building a statistics calculator using Groovy’s number features to compute mean, median, mode, standard deviation, and more.

Example 13: Statistics Calculator

class Stats {
    static BigDecimal mean(List<Number> data) {
        data.sum() / data.size()
    }

    static BigDecimal median(List<Number> data) {
        def sorted = data.sort(false)  // sort without mutating original
        def n = sorted.size()
        if (n % 2 == 1) {
            return sorted[n.intdiv(2)] as BigDecimal
        } else {
            return (sorted[n.intdiv(2) - 1] + sorted[n.intdiv(2)]) / 2.0
        }
    }

    static def mode(List<Number> data) {
        def freq = data.countBy { it }
        def maxFreq = freq.values().max()
        return freq.findAll { it.value == maxFreq }.keySet().sort()
    }

    static BigDecimal stddev(List<Number> data) {
        def avg = mean(data)
        def variance = data.collect { (it - avg) ** 2 }.sum() / data.size()
        return Math.sqrt(variance as double).toBigDecimal().setScale(4, java.math.RoundingMode.HALF_UP)
    }

    static Map summary(List<Number> data) {
        def sorted = data.sort(false)
        return [
            count : data.size(),
            min   : sorted.first(),
            max   : sorted.last(),
            range : sorted.last() - sorted.first(),
            sum   : data.sum(),
            mean  : mean(data).setScale(2, java.math.RoundingMode.HALF_UP),
            median: median(data),
            mode  : mode(data),
            stddev: stddev(data)
        ]
    }
}

// Test with sample data
def scores = [85, 92, 78, 90, 88, 76, 95, 89, 84, 91, 87, 92, 80, 88, 93]
println "Data: ${scores}"
println "\n--- Statistical Summary ---"
Stats.summary(scores).each { key, value ->
    println "${key.toString().padRight(8)}: ${value}"
}

// Another dataset
println "\n--- Temperature Data ---"
def temps = [22.5, 24.1, 19.8, 26.3, 21.0, 23.7, 25.5, 20.2, 24.8, 22.1]
println "Temps: ${temps}"
def tempStats = Stats.summary(temps)
println "Mean: ${tempStats.mean}°C"
println "Range: ${tempStats.min}°C to ${tempStats.max}°C (${tempStats.range}°C spread)"
println "Std Dev: ${tempStats.stddev}°C"

Output

Data: [85, 92, 78, 90, 88, 76, 95, 89, 84, 91, 87, 92, 80, 88, 93]

--- Statistical Summary ---
count   : 15
min     : 76
max     : 95
range   : 19
sum     : 1308
mean    : 87.20
median  : 88
mode    : [88, 92]
stddev  : 5.3907

--- Temperature Data ---
Temps: [22.5, 24.1, 19.8, 26.3, 21.0, 23.7, 25.5, 20.2, 24.8, 22.1]
Mean: 23.00°C
Range: 19.8°C to 26.3°C (6.5°C spread)
Std Dev: 2.0694°C

What happened here: This example combines nearly every number technique from this post: BigDecimal arithmetic for precision, intdiv() for integer division in the median calculation, ** for squaring in standard deviation, setScale() for controlled rounding, sort(false) to sort without mutating the original list, and Groovy’s collect/sum/countBy for functional-style data processing. The countBy method groups elements and counts occurrences – perfect for mode calculation. This is a great example of how Groovy’s number handling and collection methods work together to make data analysis concise and readable.

Common Pitfalls

These mistakes catch almost everyone coming from Java or other languages.

Pitfall 1: Integer Division Returns BigDecimal

Pitfall: Division Surprise

// BAD - expecting integer result from /
def pages = 47 / 10
println "pages: ${pages} (${pages.class.simpleName})"  // 4.7 BigDecimal!

// GOOD - use intdiv() for integer division
def correctPages = 47.intdiv(10)
println "pages: ${correctPages} (${correctPages.class.simpleName})"  // 4 Integer

Pitfall 2: Mixing BigDecimal with double

Pitfall: Precision Loss

// BAD - mixing BigDecimal with double loses precision
def price = 19.99           // BigDecimal
double taxRate = 0.08       // double
def tax = price * taxRate   // becomes double!
println "tax: ${tax} (${tax.class.simpleName})"  // 1.5992000000000002

// GOOD - keep everything as BigDecimal
def price2 = 19.99
def taxRate2 = 0.08         // also BigDecimal in Groovy
def tax2 = price2 * taxRate2
println "tax: ${tax2} (${tax2.class.simpleName})"  // 1.5992 BigDecimal

Pitfall 3: Calling Methods on Negative Literals

Pitfall: Negative Number Methods

// BAD - this is parsed as -(5.abs()), not (-5).abs()
// def result = -5.abs()  // Gives -5, not 5!

// GOOD - parenthesize negative literals
def result = (-5).abs()
println "(-5).abs() = ${result}"  // 5

// Same issue with other methods
// def r = -3.times { print it }  // Error!
def r = (-3).abs().times { print "${it} " }  // 0 1 2
println()

Conclusion

Groovy’s number handling is one of its strongest advantages over Java. The decision to make BigDecimal the default decimal type eliminates floating-point precision bugs at the source. The GDK methods – times(), upto(), downto(), abs(), round() – make numeric code read like English. The ** power operator and intdiv() fill gaps that Java leaves open. And numeric ranges bring a level of expressiveness to iteration and classification that Java simply cannot match.

The key lesson from these 13 examples: let Groovy’s defaults work for you. Don’t fight the type system by declaring double when you can use the default BigDecimal. Don’t write for (int i = 0; i < n; i++) when n.times { } says the same thing more clearly. And when precision matters – financial calculations, scientific data, billing systems – Groovy’s BigDecimal with setScale() and explicit RoundingMode gives you exact, auditable results.

For related topics, explore our Groovy Lists guide for collection operations that pair with numeric ranges, Groovy Closures for the functional programming patterns used with times() and collect(), and Groovy Strings for number-to-string formatting techniques.

Up next: Groovy More AST Transformations

Best Practices

  • DO let Groovy’s BigDecimal default handle decimal arithmetic – it’s correct by default.
  • DO use intdiv() when you specifically need integer division – the / operator returns BigDecimal.
  • DO use setScale() with an explicit RoundingMode for financial calculations.
  • DO use times(), upto(), and downto() for clean numeric loops.
  • DO use the ** power operator instead of Math.pow() – it preserves types.
  • DO validate strings with isNumber() or isInteger() before parsing.
  • DON’T mix BigDecimal and double in the same calculation – double wins and precision is lost.
  • DON’T call methods directly on negative literals without parentheses – -5.abs() is -(5.abs()), not (-5).abs().
  • DON’T assume / on integers works like Java – it returns a BigDecimal in Groovy.
  • DON’T use float or double for money – always use BigDecimal with controlled rounding.

Frequently Asked Questions

Why does Groovy use BigDecimal instead of double for decimal numbers?

Groovy uses BigDecimal as the default decimal type to avoid floating-point precision errors. In Java, 0.1 + 0.2 produces 0.30000000000000004 because double cannot represent most decimals exactly. In Groovy, 0.1 + 0.2 equals exactly 0.3 because BigDecimal stores the exact decimal value. This makes Groovy safer for financial calculations, pricing, and any math where precision matters.

How do I do integer division in Groovy?

Use the intdiv() method. In Groovy, the / operator on two integers returns a BigDecimal (so 7 / 2 gives 3.5, not 3). To get truncated integer division like Java’s / operator, call 7.intdiv(2) which returns 3. The modulo operator % works the same as Java.

What is the Groovy power operator?

Groovy uses ** as the power (exponentiation) operator. Write 2 ** 10 instead of Math.pow(2, 10). The advantage over Math.pow() is that Groovy’s ** preserves the operand types – integer bases stay as integers (promoting to Long or BigInteger as needed), and BigDecimal bases produce BigDecimal results, maintaining precision.

How do times(), upto(), and downto() work in Groovy?

These are GDK methods added to Number. 5.times { println it } runs the closure 5 times with index 0 to 4. 1.upto(10) { println it } counts from 1 to 10 (inclusive). 10.downto(1) { println it } counts from 10 down to 1 (inclusive). They replace common for loops with more readable, Groovy-idiomatic code.

What are Groovy’s number type coercion rules?

When mixing number types in arithmetic, Groovy promotes to the wider type: int + long = long, int + BigDecimal = BigDecimal, int + double = double. A critical rule: BigDecimal + double = double, which means mixing doubles with BigDecimals loses BigDecimal’s precision. For financial code, avoid double entirely and keep everything as BigDecimal. Use type suffixes like L (Long), G (BigInteger/BigDecimal), d (double), f (float) to control literal types.

Previous in Series: Groovy HTTP and REST Networking

Next in Series: Groovy More AST Transformations

Related Topics You Might Like:

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *