We can only use base-10 notation to represent decimal numbers, not hexadecimal or octal. Decimals are written with a decimal part and/or an exponent part, each with an optional + -. The leading zero is required.

[ 1.23e-23, 4.56, -1.7E1, 98.7e2, -0.27e-54 ].each{ assert it } //decimals
assert (-1.23).class == BigDecimal
assert (-1.23g).class == BigDecimal
    //BigInteger 'g' suffix after a decimal-formatted number means BigDecimal

Such BigDecimals are arbitrary-precision signed decimal numbers. They consist of an unscaled infinitely-extendable value and a 32-bit Integer scale. The value of the number represented by it is (unscaledValue × 10**(-scale)). This means a zero or positive scale is the number of digits to the right of the decimal point; a negative scale is the unscaled value multiplied by ten to the power of the negation of the scale. For example, a scale of -3 means the unscaled value is multiplied by 1000.

We can construct a BigDecimal with a specified scale:

assert new BigDecimal( 0, 1 ) == 0.0
assert new BigDecimal( 123, 0 ) == 123
assert new BigDecimal( 123 ) == 123 //default scale is 0
assert new BigDecimal( -123, 0 ) == -123
assert new BigDecimal( 123, -1 ) == 1.23e3
assert new BigDecimal( 12, -3 ) == 12000.0
assert new BigDecimal( 120, 1 ) == 12.0
assert new BigDecimal( 123, 5 ) == 0.00123
assert new BigDecimal( -123, 14 ) == -1.23e-12

assert (2 as BigDecimal).unscaledValue() == 2
assert (2 as BigDecimal).scale() == 0
assert (2 as BigDecimal).scale == 0 //parens optional
assert 2.0.unscaledValue() == 20
assert 2.0.scale == 1

All methods and constructors for this class throw NullPointerException when passed a null object reference for any input parameter.

We can enquire the scale of a BigDecimal:

assert (1234.567).unscaledValue() == 1234567g
    //returns the unscaled portion of a BigInteger

assert (1234.567).scale() == 3 //returns the scale

The precision of a BigDecimal is the number of digits in the unscaled value. The precision of a zero value is 1.

assert 7.7.precision() == 2
assert (-7.7).precision() == 2
assert 1.000.precision() == 4

We can construct a BigDecimal from a string. The value of the resulting scale must lie between Integer.MIN_VALUE and Integer.MAX_VALUE, inclusive.

assert '23.45'.toBigDecimal() == 23.45
assert new BigDecimal( '23.45' ) == 23.45
assert new BigDecimal( '-32.8e2' ) == -32.8e2
assert new BigDecimal( '+.9E-7' ) == 0.9e-7
assert new BigDecimal( '+7.E+8' ) == 7e8
assert new BigDecimal( '0.0' ) == 0.0

try{ new BigDecimal( ' 23.45' ); assert 0 }
catch(e){ assert e instanceof NumberFormatException } //whitespace in string

If we have the String in a char array and are concerned with efficiency, we can supply that array directly to the BigDecimal:

def ca1= ['1', '2', '.', '5'] as char[]
assert new BigDecimal( ca1 ) == 12.5
def ca2= [ 'a', 'b', '9', '3', '.', '4', '5', 'x', 'y', 'z' ] as char[]

assert new BigDecimal( ca2, 2, 5 ) == 93.45
    //use 5 chars from the array beginning from index 2

There are some different ways of displaying a BigDecimal:

assert 1.2345e7.toString() == '1.2345E+7'
    //one digit before decimal point, if exponent used

assert 1.2345e7.toPlainString() == '12345000' //no exponent portion
assert 1.2345e7.toEngineeringString() == '12.345E+6' //exponent divisible by 3

From Java 5.0, every distinguishable BigDecimal value has a unique string representation as a result of using toString(). If that string representation is converted back to a BigDecimal, then the original value (unscaled-scale pair) will be recovered. This means it can be used as a string representation for exchanging decimal data, or as a key in a HashMap.

[ 1.2345e7, 98.76e-3, 0.007, 0.000e4 ].each{
  assert new BigDecimal( it.toString() ) == it
}

Conversions

We can construct a BigDecimal from integers:

assert new BigDecimal( 45i ).scale == 0
assert new BigDecimal( 45L ).scale == 0

If we want to buffer frequently-used BigDecimal values for efficiency, we can use the valueOf() method:

def a= BigDecimal.valueOf( 12L, -3 )
assert a == 12000.0g && a.scale == -3

def b= BigDecimal.valueOf( 12L )
assert b == 12.0 && b.scale == 0 //default scale is 0

assert BigDecimal.ZERO == 0.0 //These commonly-used values are pre-supplied
assert BigDecimal.ONE == 1.0
assert BigDecimal.TEN == 10.0

The BigDecimal can be converted between the BigInteger, Integer, Long, Short, and Byte classes. Numbers converted to fixed-size integers may be truncated, or have the opposite sign.

assert 123g as BigDecimal == 123.0
assert 45i as BigDecimal == 45.0
assert 73L as BigDecimal == 73.0
assert 73L.toBigDecimal() == 73.0 //alternative syntax

assert 123.456 as BigInteger == 123g //lost information about the precision
assert 123.456.toBigInteger() == 123g //alternative syntax
assert 73.0 as Long == 73g
assert 73.0 as long == 73g
assert 73.0.toLong() == 73g
assert 73.0.longValue() == 73g //another alternative syntax
assert 45.6789.intValue() == 45g //truncated
assert 259.0.byteValue() == 3 //truncated, only lowest 8 integral bits returned
assert 200.789.byteValue() == -56
    //truncated, only lowest 8 integral bits returned, with opposite sign

By appending 'Exact' to the asLong()-style method names, we can ensure an ArithmeticException is thrown if any information would be lost in the conversion:

assert 123.0.toBigIntegerExact() == 123g //lost information about the precision
try{ 123.456.toBigIntegerExact(); assert false }
catch(e){ assert e instanceof ArithmeticException }

assert 73.0.longValueExact() == 73g

[ { 73.21.longValueExact() },
  { 45.6789.intValueExact() },
  { 73.21.shortValueExact() },
  { 259.0.byteValueExact() },
  { 200.789.byteValueExact() },
].each{
  try{ it(); assert false }catch(e){ assert e instanceof ArithmeticException }
}

BigDecimal Arithmetic

We can use the same methods and operators on BigDecimal we use with BigInteger:

assert 3.4.plus( 3.3 ) == 3.4 + 3.3
assert 3.4.add( 3.3 ) == 3.4 + 3.3 //alternative name for plus
assert 3.4.minus( 2.1 ) == 3.4 - 2.1
assert 3.4.subtract( 2.1 ) == 3.4 - 2.1 //alternative name for minus
assert 3.0.multiply( 3.1 ) == 3.0 * 3.1
assert 3.0.multiply( 3g ) == 3.0 * 3g
assert 7.7.negate() == -7.7 //unary operation/method
assert (-7.7).negate() == -(-7.7)
assert (-7.7).plus() == +(-7.7) //this method provided for symmetry with negate

try{ 3.4.multiply(null); assert 0 }
catch(e){ assert e instanceof NullPointerException }
    //all BigDecimal methods throw NullPointerException if passed a null

The scale resulting from add or subtract is the maximum scale of each operand; that resulting from multiply is the sum of the scales of the operands:

def a = 3.414, b = 3.3
assert a.scale() == 3 && b.scale() == 1
assert (a+b).scale() == 3 //max of 3 and 1
assert (a*b).scale() == 4 //sum of 3 and 1

For + - and *, a BigDecimal with any integer type converts it to a BigDecimal:

assert (123.45g * 789).class == BigDecimal
assert (123.45g * 789L).class == BigDecimal
assert (123.45g * (89 as byte)).class == BigDecimal

We can use a MathContext to change the precision of operations involving BigDecimals:

def mc= new java.math.MathContext( 3 )
    //precision of 3 in all constructors and methods where used
assert new BigDecimal( 123456, 0, mc ) == 123000g
assert new BigDecimal( -12345, 14, mc ) == -1.23e-10
assert new BigDecimal( '23.4567', mc ) == 23.5
assert new BigDecimal(
    ['2', '3', '.', '4', '5', '6', '7'] as char[], mc ) == 23.5
assert new BigDecimal(
    ['2', '3', '.', '4', '5', '6', '7'] as char[], 1, 5, mc ) == 3.46
assert new BigDecimal( 1234i, mc ) == 1230
assert new BigDecimal( 1234L, mc ) == 1230

assert 3.45678.add( 3.3, mc ) == 6.76
assert 0.0.add( 3.333333, mc ) == 3.33
assert 3.4567.subtract( 2.1, mc ) == 1.36
assert 0.0.subtract( 2.12345, mc ) == -2.12
assert 3.0.multiply( 3.1234, mc ) == 9.37
assert (-7.77777).negate( mc ) == 7.78

assert (-7.77777).plus( mc ) == -7.78
    //effect identical to that of round(MathContext) method

Division

We can create BigDecimals by dividing integers, both fixed-size and BigInteger, for which the result is a decimal number:

assert 7g / 4g == 1.75
assert (-7g) / 4g == -1.75
assert ( 1 / 2 ).class == BigDecimal
assert ( 1L / 2L ).class == BigDecimal
assert ( 1g / 2g ).class == BigDecimal

assert ( 1.5 * 2g ).class == BigDecimal
    //an expression with a BigDecimal never converts to an integer

assert 1.0.div( 2 ).class == BigDecimal
    //we can use a method instead of the operator

try{ 17g / 0; assert 0 }catch(e){ assert e instanceof ArithmeticException }
    //division by 0 not allowed

Sometimes, the division can return a recurring number. This leads to a loss of exactness:

assert 1/3 == 0.3333333333
    //BigDecimals with recurring decimals round their result to 10 places...
assert ( (1/3) * 3 ) != 1
    //...which leads to inaccuracy in calculations
assert (1/3).precision() == 10
assert 100000/3 == 33333.3333333333
   //accuracy before the decimal point is always retained

When the scales of both operands in division are quite different, we can lose precision, sometimes even completely:

assert (1.0 / 7.0) == 0.1428571429
    //instead of "0.142857 with last 6 digits recurring"
assert (1e-5 / 7.0) == 0.0000014286 //precision is 10
assert (1e-9 / 7.0) == 0.0000000001
assert (1e-11 / 7.0) == 0.0
    //difference in scale of operands can cause full loss of precision

The ulp() of a BigDecimal returns the "Units of the Last Place", the difference between the value and next larger having the same number of digits:

assert 123.456.ulp() == 0.001 //always 1, but with same scale
assert 123.456.ulp() == (-123.456).ulp()
assert 0.00.ulp() == 0.01

Another way of dividing numbers is to use the divide() method, different to the div() method and / operator. The result must be exact when using divide(), or an ArithmeticException is thrown.

assert 1.0.divide( 4.0 ) == 0.25

try{ 1.0.divide( 7.0 ); assert 0 }
catch(e){ assert e instanceof ArithmeticException }
    //result must be exact when using divide()

assert 1.234.divide( 4.0 ) == 0.3085
assert 1.05.divide( 1.25 )
assert 1.234.scale() == 3 && 4.0.scale() == 1 && 0.3085.scale() == 4
    //scale of result unpredictable
assert 1.05.scale() == 2 && 1.25.scale() == 2 && 0.84.scale() == 2

We can change the precision of divide() by using a MathContext:

assert (1.0).divide( 7.0, new java.math.MathContext(12) ) == 0.142857142857
                                                               //precision is 12

assert (1.0).divide( 7.0, new java.math.MathContext(10) ) == 0.1428571429

assert (1.0).divide( 7.0, new java.math.MathContext(5) ) == 0.14286

try{ 1.0.divide( 7.0, new java.math.MathContext(0) ); assert 0 }
catch(e){ assert e instanceof ArithmeticException }
    //precision of 0 same as if no MathContext was supplied

MathContext Rounding Modes

As well as specifying required precision for operations in a MathContext, we can also specify the rounding behavior for operations discarding excess precision. Each rounding mode indicates how the least significant returned digit of a rounded result is to be calculated.

If fewer digits are returned than the digits needed to represent the exact numerical result, the discarded digits are called "the discarded fraction", regardless their contribution to the value of the number returned. When rounding increases the magnitude of the returned result, it is possible for a new digit position to be created by a carry propagating to a leading 9-digit. For example, the value 999.9 rounding up with three digits precision would become 1000.

We can see the behaviour of rounding operations for all rounding modes:

import java.math.MathContext
import java.math.RoundingMode
    //so we don't have to qualify these with java.math when we refer to them
import static java.math.RoundingMode.*
    //so we don't have to qualify UP, DOWN, etc with java.math.RoundingMode

def values=  [   +5.5, +2.5, +1.6, +1.1, +1.0, -1.0, -1.1, -1.6, -2.5, -5.5 ]
def results= [
         (UP): [    6,    3,    2,    2,    1,   -1,   -2,   -2,   -3,   -6 ],
       (DOWN): [    5,    2,    1,    1,    1,   -1,   -1,   -1,   -2,   -5 ],
    (CEILING): [    6,    3,    2,    2,    1,   -1,   -1,   -1,   -2,   -5 ],
      (FLOOR): [    5,    2,    1,    1,    1,   -1,   -2,   -2,   -3,   -6 ],
    (HALF_UP): [    6,    3,    2,    1,    1,   -1,   -1,   -2,   -3,   -6 ],
  (HALF_DOWN): [    5,    2,    2,    1,    1,   -1,   -1,   -2,   -2,   -5 ],
  (HALF_EVEN): [    6,    2,    2,    1,    1,   -1,   -1,   -2,   -2,   -6 ],
]

results.keySet().each{ roundMode->
  def mc= new MathContext( 1, roundMode )
  results[ roundMode ].eachWithIndex{ it, i->
    assert new BigDecimal( values[i], mc ) == it
  }
}

def mcu= new MathContext( 1, UNNECESSARY )
assert new BigDecimal( 1.0, mcu ) == 1
assert new BigDecimal( -1.0, mcu ) == -1
[ +5.5, +2.5, +1.6, +1.1, -1.1, -1.6, -2.5, -5.5 ].each{
  try{ new BigDecimal( it, mcu ); assert 0 }
  catch(e){ assert e instanceof ArithmeticException }
}

We can thus see:
UP rounds away from zero, always incrementing the digit prior to a non-zero discarded fraction.
DOWN rounds towards zero, always truncating.
CEILING rounds towards positive infinity (positive results behave as for UP; negative results, as for DOWN).
FLOOR rounds towards negative infinity (positive results behave as for DOWN; negative results, as for UP).
HALF_UP rounds towards nearest neighbor; if both neighbors are equidistant, rounds as for UP. (The rounding mode commonly taught in US schools.)
HALF_DOWN rounds towards nearest neighbor; if both neighbors are equidistant, rounds as for DOWN.
HALF_EVEN rounds towards the nearest neighbor; if both neighbors are equidistant, rounds towards the even neighbor. (Known as "banker's rounding.")
UNNECESSARY asserts that the operation has an exact result; if there's an inexact result, throws an ArithmeticException.

There are some default rounding modes supplied for use:

import java.math.*
    //imports all such classes, including both MathContext and RoundingMode

MathContext.UNLIMITED
    //for unlimited precision arithmetic (precision=0 roundingMode=HALF_UP)
MathContext.DECIMAL32
    //for "IEEE 754R" Decimal32 format (precision=7 roundingMode=HALF_EVEN)
MathContext.DECIMAL64
    //Decimal64 format (precision=16 roundingMode=HALF_EVEN)
MathContext.DECIMAL128
    //Decimal128 format (precision=34 roundingMode=HALF_EVEN)

assert MathContext.DECIMAL32.precision == 7

assert MathContext.DECIMAL32.roundingMode == RoundingMode.HALF_EVEN
    //precision and roundingMode are properties

assert new BigDecimal( 123456789, 0, MathContext.DECIMAL32 ) == 123456800g

Other constructors for MathContext are:

import java.math.*
def mc1= new MathContext( 3 )
    //by default, uses RoundingMode.HALF_UP rounding mode
assert mc1.roundingMode == RoundingMode.HALF_UP

def mc2= new MathContext( 3, RoundingMode.HALF_UP )
assert mc2.toString() == 'precision=3 roundingMode=HALF_UP'
def mc3= new MathContext( mc2.toString() )
    //we can initialize a MathContext from another's string
assert mc3.precision == 3
assert mc3.roundingMode == RoundingMode.HALF_UP

The rounding mode setting of a MathContext object with a precision setting of 0 is not used and thus irrelevant.

Cloning BigDecimals but with different scale

We can create a new BigDecimal with the same overall value as but a different scale to an existing one:

import java.math.*

def num= 2.2500
assert num.scale == 4 && num.unscaledValue() == 22500
def num2= num.setScale(5)
assert num2 == 2.25000 && num2.scale == 5 && num2.unscaledValue() == 225000
    //usual use of changing scale is to increase the scale
def num3= num.setScale(3)
assert num3 == 2.25000 && num3.scale == 3 && num3.unscaledValue() == 2250

assert num.setScale(2) == 2.25
    //only BigDecimal returned from method call has changed scale...
assert num.scale == 4 //...while original BigDecimal still has old scale...
num.scale= 3 //...so there's no point using the allowable property syntax
assert num.scale == 4

try{
  num.setScale(1) //we can't change the value when we reduce the scale...
  assert false
}catch(e){ assert e instanceof ArithmeticException }
assert 1.125.setScale(2, RoundingMode.HALF_UP) == 1.13
                                        //...unless we use a rounding mode
assert 1.125.setScale(2, BigDecimal.ROUND_HALF_UP) == 1.13 //pre-Java-5 syntax

These 8 BigDecimal static fields are older pre-Java-5.0 equivalents for the values in the RoundingMode enum:
BigDecimal.ROUND_UP
BigDecimal.ROUND_DOWN
BigDecimal.ROUND_CEILING
BigDecimal.ROUND_FLOOR
BigDecimal.ROUND_HALF_UP
BigDecimal.ROUND_HALF_DOWN
BigDecimal.ROUND_HALF_EVEN
BigDecimal.ROUND_UNNECESSARY

There's two methods that let us convert such older names to the newer RoundingMode constants (enums):

import java.math.RoundingMode
assert RoundingMode.valueOf( 'HALF_UP' ) == RoundingMode.HALF_UP

assert RoundingMode.valueOf( BigDecimal.ROUND_HALF_DOWN ) ==
    RoundingMode.HALF_DOWN

Further operations

For the other arithmetic operations, we also usually have the choice of supplying a MathContext or not.

There's two main ways to raise a number to a power. Using ** and power() returns a fixed-size floating-point number, which we'll look at in the next topic on Groovy Floating-Point Math.

assert (4.5**3).class == Double
assert 4.5.power(3).class == Double //using equivalent method instead

We can raise a BigDecimal to the power using the pow() method instead, which always returns an exact BigDecimal. However, this method will be very slow for high exponents. The result can sometimes differ from the rounded result by more than one ulp (unit in the last place).

assert 4.5.pow(3) == 91.125 //pow() is different to power()
assert (-4.5).pow(3) == -91.125
assert 4.5.pow(0) == 1.0
assert 0.0.pow(0) == 1.0
try{ 4.5.pow(-1); assert 0 }catch(e){ assert e instanceof ArithmeticException }
    //exponent must be integer >=0
try{ 1.1.pow(1000000000); assert 0 }
catch(e){ assert e instanceof ArithmeticException }
    //exponent too high for Java 5

//println( 1.1.pow(999999999) )
    //warning: this runs for a VERY LONG time when uncommented

When we supply a MathContext, the "ANSI X3.274-1996" algorithm is used:

import java.math.MathContext
assert 4.5.pow( 3, new MathContext(4) ) == 91.13 //can supply a MathContext
assert 4.5.pow( -1, new MathContext(10) )
    //negative exponents allowed when MathContext supplied
try{ 4.5.pow( -1, new MathContext(0) ); assert 0 }
catch(e){ assert e instanceof ArithmeticException }
    //ArithmeticException thrown if mc.precision == 0 and n < 0
try{ 4.5.pow( 123, new MathContext(2) ); assert 0 }
catch(e){ assert e instanceof ArithmeticException }
    //ArithmeticException thrown if mc.precision > 0 and
    //n has more than mc.precision decimal digits

Instead of giving a precision via the MathContext, we can give the desired scale directly:

import java.math.RoundingMode
assert 25.497.divide( 123.4567, 5, RoundingMode.UP ) == 0.20653
    //specify desired scale of 4, and rounding mode UP
assert 25.497.divide( 123.4567, 5, BigDecimal.ROUND_UP ) == 0.20653
    //cater for pre-Java-5.0 syntax
assert 25.497.divide( 123.4567, RoundingMode.UP ) == 0.207
    //if no scale given, use same one as dividend (here, 25.497)
assert 25.497.divide( 123.4567, BigDecimal.ROUND_UP ) == 0.207

We can divide to an integral quotient, and/or find the remainder. (The preferred scale of the integral quotient is the dividend's less the divisor's.)

import java.math.*
mc= new MathContext( 9, RoundingMode.HALF_UP )
assert 25.5.divide( 2.4, mc ) == 10.625

assert 25.5.divideToIntegralValue( 2.4 ) == 10 //rounding mode always DOWN...
assert 25.5.remainder( 2.4 ) == 1.5
assert 25.5.divideToIntegralValue( 2.4, mc ) == 10
                             //...even when a MathContext says otherwise
assert 25.5.remainder( 2.4, mc ) == 1.5
assert (-25.5).divideToIntegralValue( 2.4, mc ) == -10
assert (-25.5).remainder( 2.4, mc ) == -1.5

try{ 25.5.divideToIntegralValue( 0 ); assert 0 }
catch(e){ assert e instanceof ArithmeticException }

try{ 25.5.remainder( 0 ); assert 0 }
catch(e){ assert e instanceof ArithmeticException }

assert 25.525.remainder( 2.345, new MathContext(1) ) == 2.075
    //MathContext's precision only affects quotient calculation;
    //remainder always exact so may have more decimal digits

[ [25.5, 2.4], [-27.1, 3.3] ].each{ x, y->
  assert x.remainder( y ) ==
      x.subtract( x.divideToIntegralValue( y ).multiply( y ) ) 
}

try{
  2552.0.divideToIntegralValue( 2.4, new MathContext(2) )
  assert 0
}catch(e){ assert e instanceof ArithmeticException }
    //if result needs more decimal digits than supplied MathContext's precision

try{
  2552.0.remainder( 2.4, new MathContext(2) )
  assert 0
}catch(e){ assert e instanceof ArithmeticException }
    //throw if implicit divideToIntegralValue() result needs more decimal digits
    //than supplied MathContext's precision

def qr= 25.5.divideAndRemainder( 2.4 )
assert qr[0] == 10 && qr[1] == 1.5
  //same results as divideToIntegralValue() and remainder(), but more efficient

We can find the absolute value of a BigDecimal:

import java.math.*
assert 7.89.abs() == 7.89 //same scale if no MathContext
assert (-7.89).abs() == 7.89
assert (-7.89).abs( new MathContext(2) ) == 7.9

The round() operation only has a version with a MathContext parameter. Its action is identical to that of the plus(MathContext) method.

assert 7.89.round( new MathContext(2) ) == 7.9
assert 7.89.round( new MathContext(0) ) == 7.89 //no rounding if precision is 0

Operations without a MathContext

Not all BigDecimal operations have a MathContext.

Auto-incrementing and -decrementing work on BigDecimals:

def a= 12.315
a++
assert a == 13.315
--a
assert a == 12.315

The signum method:

assert 2.34.signum() == 1
assert (-2.34).signum() == -1
assert 0.0.signum() == 0

As with integers, we can compare BigDecimals:

assert (2.50 <=> 2.5) == 0 && 2.50.compareTo(2.5) == 0
assert (-3.45 <=> 1.23) == -1 && (-3.45).compareTo(1.23)  == -1
assert (1.23 <=> -0.12) == 1 && 1.23.compareTo(-0.12) == 1
assert (1.23 > -0.12) && 1.23.compareTo(-0.12) > 0

The equals() method and == operator are different for BigDecimals. (So we must be careful if we use BigDecimal objects as elements in a SortedSet or keys in a SortedMap, since BigDecimal's natural ordering is inconsistent with equals().)

assert ! ( 2.00.equals(2.0) )
    //considers whether both unscaledValue and scale are equal
assert 2.00 == 2.0 //only considers the sequence of the two numbers on a line

assert 0.0 == -0.0 && 0.0.equals( -0.0 )

We can find the minimum and maximum of two BigDecimals:

assert (-2.0).min( 7.3 ) == -2.0
assert 3.5.max( 4.2 ) == 4.2

We can move the decimal point to the left or right:

import java.math.*
def num= 123.456
assert num.scale == 3
def mpl= num.movePointLeft( 2 )
assert mpl.scale == 5 //scale should be max( number.scale + movement, 0 )
assert mpl == 1.23456
def mpr= num.movePointRight( 4 )
assert mpr.scale == 0 //scale should be max( number.scale - movement, 0 )
assert mpr == 1234560
assert( 3.456.movePointLeft(2) == 0.03456 )
[ -2, -1, 0, 1, 2 ].each{
  assert 123.456.movePointLeft( it ) == 123.456.movePointRight( -it )
}
try{ //throw ArithmeticException if scale will overflow on moving decimal point
  new BigDecimal( 123456, 128*256*256*256 - 1 ).movePointLeft( 1 )
  assert 0
}catch(e){ assert e instanceof ArithmeticException }

Another method for moving the decimal point, but by consistent change to the scale:

import java.math.*
def num= 123.456
assert num.scale == 3
def mpl= num.scaleByPowerOfTen( 16 )
assert mpl == 1.23456e18
assert mpl.scale == -13  //num.scale - 16

We can strip trailing zeros:

assert 45.607000.stripTrailingZeros() == 45.607
assert 600.0.stripTrailingZeros() == 6e2
assert new BigDecimal( 6000, 1 ).stripTrailingZeros() == new BigDecimal( 6, -2 )