Question
Answer and Explanation
Floating-point precision issues in Java, particularly when using assertEquals
in JUnit tests, arise because floating-point numbers (like float
and double
) are stored in memory using a binary representation. Not all decimal fractions can be represented exactly in binary, leading to small discrepancies. This can cause tests to fail even when the results are practically equivalent. Here's how you can address these issues:
1. Use a Delta for Comparison:
- The most common solution is to use the assertEquals(expected, actual, delta)
method. This allows you to specify a tolerance (delta) within which the actual and expected values are considered equal. The delta represents the maximum difference allowed between the two values.
- Example:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FloatingPointTest {
@Test
void testFloatingPointEquality() {
double expected = 1.0 / 3.0;
double actual = 0.3333333;
double delta = 0.0000001;
assertEquals(expected, actual, delta);
}
}
- In this example, delta
is set to 0.0000001. The test will pass as long as the absolute difference between expected
and actual
is less than or equal to delta
.
2. Choose an Appropriate Delta Value:
- Selecting the right delta
is crucial. It depends on the magnitude of the numbers and the precision requirements of your calculations. A very small delta
may still lead to failures, while a large delta
may hide genuine errors.
3. Avoid Direct Equality Comparisons:
- Never compare floating-point numbers directly with ==
or !=
. These comparisons are unreliable due to precision limitations. Always use a tolerance-based comparison.
4. Use BigDecimal for Exact Arithmetic:
- If exact decimal arithmetic is required (for example, when dealing with financial data), use the BigDecimal
class. BigDecimal
provides arbitrary-precision decimal numbers, avoiding the rounding and imprecision issues inherent in float
and double
.
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class BigDecimalTest {
@Test
void testBigDecimalEquality() {
BigDecimal expected = new BigDecimal("1.0").divide(new BigDecimal("3.0"), 10, BigDecimal.ROUND_HALF_UP);
BigDecimal actual = new BigDecimal("0.3333333333");
assertEquals(expected, actual);
}
}
- Note, When using BigDecimal
make sure to round to expected precision when doing division. In the example above the expected result is rounded to 10 decimal places.
5. Custom Comparison Methods:
- In more complex situations, it may be useful to write a custom comparison method that encapsulates the tolerance check. This promotes code reusability and can improve readability.
By using a delta for comparison or opting for BigDecimal
when precision is paramount, you can effectively address floating-point precision issues when using assertEquals
in Java unit tests.