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.
Table of Contents
Quick Reference Table
| Feature | Syntax | Example | Result |
|---|---|---|---|
| BigDecimal literal | 3.14 | 3.14.class | java.math.BigDecimal |
| Power operator | ** | 2 ** 10 | 1024 |
| Integer division | intdiv() | 7.intdiv(2) | 3 |
| Times loop | N.times { } | 3.times { print it } | 012 |
| Upto loop | a.upto(b) { } | 1.upto(3) { print it } | 123 |
| Downto loop | a.downto(b) { } | 3.downto(1) { print it } | 321 |
| Absolute value | abs() | (-5).abs() | 5 |
| Round | round(n) | 3.456.round(2) | 3.46 |
| Numeric range | a..b | (1..5).toList() | [1, 2, 3, 4, 5] |
| Type coercion | automatic | (2 + 3.0).class | java.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 returnsBigDecimal. - DO use
setScale()with an explicitRoundingModefor financial calculations. - DO use
times(),upto(), anddownto()for clean numeric loops. - DO use the
**power operator instead ofMath.pow()– it preserves types. - DO validate strings with
isNumber()orisInteger()before parsing. - DON’T mix
BigDecimalanddoublein 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 aBigDecimalin Groovy. - DON’T use
floatordoublefor money – always useBigDecimalwith 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.
Related Posts
Previous in Series: Groovy HTTP and REST Networking
Next in Series: Groovy More AST Transformations
Related Topics You Might Like:
- Groovy Lists – Collection Operations
- Groovy Closures – Functional Programming
- Groovy Operators – Complete Guide
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment