Rounding Decimals in Java: Avoiding Round-Off Errors with BigDecimal

The following Java code demonstrates different ways to round a double to the nearest integer or any specific decimal place (tenths, hundredths, thousandths, etc.).

NOTE: The key takeaway is to use the BigDecimal rounding method (Method 4) detailed below. Other methods have precision issues that can lead to unreliable results, especially for calculations that require high accuracy (for example, putting someone on the Moon). BigDecimal handles both positive and negative numbers, as well as zero.

public static String roundDecimalUsingBigDecimal(String strValue, int decimalPlace) {
  return new BigDecimal(strValue).setScale(decimalPlace,
    RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
}

Why Is Rounding So Complicated? Round-off Errors!

Round-off errors are typically covered early in a Computer Science curriculum. In short, computers represent numbers in binary, which creates limitations when working with fractional values. This is a limitation of how fractional numbers are represented in a finite binary storage model, not something specific to Java.

For example, the fraction 1/3 cannot be accurately represented in binary since it has an infinite, non-terminating expansion (for example, 0.333...). Floating-point primitives have a fixed storage size, and when there are not enough bits to represent a number precisely, small errors occur.

Here is an example demonstrating round-off errors when adding 0.1 and 0.2:

public class RoundOffErrorExample {
  public static void main(String[] args) {
    double dValue1 = 0.1d;
    double dValue2 = 0.2d;
    double dResult = dValue1 + dValue2;

    System.out.println("dValue1 = " + dValue1);
    System.out.println("dValue2 = " + dValue2);
    System.out.println("dResult = " + dResult);
  }
}

The expected value is 0.3. However, the actual result is 0.30000000000000004.

dValue1 = 0.1
dValue2 = 0.2
dResult = 0.30000000000000004

While the discrepancy seems small, these errors can accumulate and cause significant issues, especially when performing many calculations. For example, in a banking application handling millions of transactions, even small errors can add up and cause material financial impacts. This was famously portrayed in Superman III, where a character embezzled round-off errors from a bank’s system, a technique known as salami slicing or penny shaving.

How to Handle Decimal Rounding in Java

Below are four methods to round decimals in Java, each with its own limitations. As a general rule, use BigDecimal to get the most reliable and accurate results.

The first 50 decimal digits of pi are used for demonstration:

3.14159265358979323846264338327950288419716939937510

Method 1: Using Math.round()

Result: Rounded incorrectly

This method only rounds to the nearest integer, making it unsuitable for rounding to decimal places.

Using the first 50 decimal digits of pi, the result is 3.

Method 1: Rounded using Math.round()
Input value: 3.14159265358979323846264338327950288419716939937510
--------
Rounded using Math.round(): 3

Method 2: Using Powers of 10

Result: Rounded incorrectly

This method involves multiplying by powers of 10, converting to a long, and then dividing by powers of 10. While it may work for small values, it breaks down for larger numbers. At 19 and 20 decimal places, the intermediate value exceeds Long.MAX_VALUE, so the cast to long saturates rather than rounding, and dividing that saturated value back down produces an unrelated result rather than the expected number.

Method 2: Rounded using Powers of 10
Input value: 3.14159265358979323846264338327950288419716939937510
--------
Rounded to  0 decimal places: 3.0
Rounded to  1 decimal places: 3.1
Rounded to  2 decimal places: 3.14
Rounded to  3 decimal places: 3.142
Rounded to  4 decimal places: 3.1416
Rounded to  5 decimal places: 3.14159
Rounded to  6 decimal places: 3.141593
Rounded to  7 decimal places: 3.1415927
Rounded to  8 decimal places: 3.14159265
Rounded to  9 decimal places: 3.141592654
Rounded to 10 decimal places: 3.1415926536
Rounded to 11 decimal places: 3.14159265359
Rounded to 12 decimal places: 3.14159265359
Rounded to 13 decimal places: 3.1415926535898
Rounded to 14 decimal places: 3.14159265358979
Rounded to 15 decimal places: 3.141592653589793
Rounded to 16 decimal places: 3.141592653589793
Rounded to 17 decimal places: 3.141592653589793
Rounded to 18 decimal places: 3.141592653589793
Rounded to 19 decimal places: 0.9223372036854776
Rounded to 20 decimal places: 0.09223372036854775

Method 3: Using DecimalFormat

Result: Rounded incorrectly

This method suffers from the same precision issues and fails when rounding past 15 decimal places for this input value.

Method 3: Rounded using DecimalFormat
Input value: 3.14159265358979323846264338327950288419716939937510
--------
Rounded to  0 decimal places: 3
Rounded to  1 decimal places: 3.1
Rounded to  2 decimal places: 3.14
Rounded to  3 decimal places: 3.142
Rounded to  4 decimal places: 3.1416
Rounded to  5 decimal places: 3.14159
Rounded to  6 decimal places: 3.141593
Rounded to  7 decimal places: 3.1415927
Rounded to  8 decimal places: 3.14159265
Rounded to  9 decimal places: 3.141592654
Rounded to 10 decimal places: 3.1415926536
Rounded to 11 decimal places: 3.14159265359
Rounded to 12 decimal places: 3.14159265359
Rounded to 13 decimal places: 3.1415926535898
Rounded to 14 decimal places: 3.14159265358979
Rounded to 15 decimal places: 3.141592653589793
Rounded to 16 decimal places: 3.141592653589793
Rounded to 17 decimal places: 3.141592653589793
Rounded to 18 decimal places: 3.141592653589793
Rounded to 19 decimal places: 3.141592653589793
Rounded to 20 decimal places: 3.141592653589793

Method 4: Using BigDecimal

Result: Rounded correctly

This is the recommended method for rounding decimals in Java. BigDecimal handles large and small numbers without round-off errors, ensuring precise results. Unlike the other three methods, it operates on the original string value rather than a double, so it never inherits the roughly 15 to 17 significant-digit ceiling that limits double precision.

Method 4: Rounded using BigDecimal
Input value: 3.14159265358979323846264338327950288419716939937510
--------
Rounded to  0 decimal places: 3
Rounded to  1 decimal places: 3.1
Rounded to  2 decimal places: 3.14
Rounded to  3 decimal places: 3.142
Rounded to  4 decimal places: 3.1416
Rounded to  5 decimal places: 3.14159
Rounded to  6 decimal places: 3.141593
Rounded to  7 decimal places: 3.1415927
Rounded to  8 decimal places: 3.14159265
Rounded to  9 decimal places: 3.141592654
Rounded to 10 decimal places: 3.1415926536
Rounded to 11 decimal places: 3.14159265359
Rounded to 12 decimal places: 3.14159265359
Rounded to 13 decimal places: 3.1415926535898
Rounded to 14 decimal places: 3.14159265358979
Rounded to 15 decimal places: 3.141592653589793
Rounded to 16 decimal places: 3.1415926535897932
Rounded to 17 decimal places: 3.14159265358979324
Rounded to 18 decimal places: 3.141592653589793238
Rounded to 19 decimal places: 3.1415926535897932385
Rounded to 20 decimal places: 3.14159265358979323846

Usage

The following code tests all four rounding methods. To run it, execute the command:

java RoundingMethods [value]

[value] is the number to round; multiple numbers can be passed, separated by spaces.

To recreate the example used in this article, execute the command:

java RoundingMethods 3.14159265358979323846264338327950288419716939937510
import java.lang.Math;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

public class RoundingMethods {
  public static void main(String[] args) {
    double dValue = 0.0 d;
    int i = 0;

    for (String strValue: args) {
      dValue = Double.parseDouble(strValue);

      // Method 1: Double rounded to the nearest integer using Math.round().
      System.out.println("");
      System.out.println("Method 1: Rounded using Math.round()");
      System.out.println("Input value: " + strValue);
      System.out.println("--------");
      System.out.println("Rounded using Math.round(): " + Math.round(dValue));

      // Method 2: Double rounded to any decimal place by multiplying by a power of
      // 10, converting to long, and dividing by a power of 10.
      System.out.println("");
      System.out.println("Method 2: Rounded using Powers of 10");
      System.out.println("Input value: " + strValue);
      System.out.println("--------");
      for (i = 0; i <= 20; i++) {
        System.out.println("Rounded to " + i + " decimal places: " +
          RoundingMethods.roundDecimalUsingPowers(dValue, i));
      }

      // Method 3: Double rounded to any decimal place using DecimalFormat.
      System.out.println("");
      System.out.println("Method 3: Rounded using DecimalFormat");
      System.out.println("Input value: " + strValue);
      System.out.println("--------");
      for (i = 0; i <= 20; i++) {
        System.out.println("Rounded to " + i + " decimal places: " +
          RoundingMethods.roundDecimalUsingDecimalFormat(dValue, i));
      }

      // Method 4: Double rounded to any decimal place using BigDecimal.
      System.out.println("");
      System.out.println("Method 4: Rounded using BigDecimal");
      System.out.println("Input value: " + strValue);
      System.out.println("--------");
      for (i = 0; i <= 20; i++) {
        System.out.println("Rounded to " + i + " decimal places: " +
          RoundingMethods.roundDecimalUsingBigDecimal(strValue, i));
      }
    }
  }

  public static double roundDecimalUsingPowers(double dValue, int decimalPlace) {
    return ((long)((dValue * Math.pow(10.0, decimalPlace)) + ((dValue < 0.0) ? -0.5 : 0.5))) /
      Math.pow(10.0, decimalPlace);
  }

  public static String roundDecimalUsingDecimalFormat(double dValue, int decimalPlace) {
    DecimalFormat decimalFormat = null;
    StringBuffer stringBuffer = null;

    stringBuffer = new StringBuffer("#");
    if (decimalPlace > 0) {
      stringBuffer.append(".");
    }

    for (int i = 1; i <= decimalPlace; i++) {
      stringBuffer.append("#");
    }

    decimalFormat = new DecimalFormat(stringBuffer.toString());
    decimalFormat.setRoundingMode(RoundingMode.HALF_UP);

    return decimalFormat.format(dValue);
  }

  public static String roundDecimalUsingBigDecimal(String strValue, int decimalPlace) {
    return new BigDecimal(strValue).setScale(decimalPlace,
      RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
  }
}

Results

In the original example of adding 0.1 and 0.2, the code is modified to use BigDecimal for precise calculations.

import java.math.BigDecimal;

public class BigDecimalExample {
  public static void main(String[] args) {
    BigDecimal bdValue1 = new BigDecimal("0.1");
    BigDecimal bdValue2 = new BigDecimal("0.2");
    BigDecimal bdResult = bdValue1.add(bdValue2);

    System.out.println("bdValue1 = " + bdValue1.toPlainString());
    System.out.println("bdValue2 = " + bdValue2.toPlainString());
    System.out.println("bdResult = " + bdResult.toPlainString());
  }
}

The result is 0.3 as expected.

bdValue1 = 0.1
bdValue2 = 0.2
bdResult = 0.3

Summary

When dealing with decimal rounding in Java, BigDecimal is the most reliable method for achieving precise results without round-off errors. Other methods, such as Math.round(), powers of 10, and DecimalFormat, can introduce inaccuracies, especially for large or highly precise numbers. For critical calculations, always prefer BigDecimal to ensure accuracy and prevent potential issues from accumulating.