Math #

Mathematical operations in Java look simple on the surface — there are the +, -, *, / operators and the Math class with various functions. But hidden underneath are invisible traps: integer overflow that silently produces wrong numbers, floating-point division that’s inaccurate for financial calculations, and double values that can’t precisely represent many decimal values. Understanding the limitations of Java’s numeric types and knowing when to replace them with BigDecimal or BigInteger is the skill that separates developers who write code that “looks right” from developers who write code that’s actually right. This article covers Java’s entire math toolkit — from the simplest Math.abs() to BigDecimal for financial transactions and SecureRandom for cryptography.

The Math Class — Standard Math Functions #

java.lang.Math contains static methods for all common mathematical operations. All methods use the double type unless explicitly stated otherwise.

Absolute Values, Min, and Max #

// abs() — absolute value
Math.abs(-5);        // 5
Math.abs(-3.14);     // 3.14
Math.abs(Integer.MIN_VALUE); // TRAP! the result is Integer.MIN_VALUE because of overflow!
// Integer.MIN_VALUE = -2147483648, and Math.abs(-2147483648) overflows back to -2147483648

// ✓ CORRECT: use Math.absExact() (Java 15+) for overflow detection
try {
    Math.absExact(Integer.MIN_VALUE); // throws ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("Overflow detected: " + e.getMessage());
}

// min() and max()
Math.min(3, 7);        // 3
Math.max(3, 7);        // 7
Math.min(3.5, 2.1);   // 2.1
Math.min(Double.NaN, 3.0); // NaN — NaN "infects" the comparison

// clamp() — Java 21+
// ensures a value stays within the range [min, max]
double value = 150.0;
double clamped = Math.clamp(value, 0.0, 100.0); // 100.0
int clampedInt = Math.clamp(250, 0, 100);        // 100

Rounding #

// Four different types of rounding — choose the right one for the context
double d = 2.7;

Math.ceil(d);    // 3.0  — always up (ceiling)
Math.floor(d);   // 2.0  — always down (floor)
Math.round(d);   // 3L   — round half up (0.5 rounds up), returns a long
Math.rint(d);    // 3.0  — round half even (banker's rounding), returns a double

// Rounding 0.5 examples
Math.round(2.5);   // 3  — round half up
Math.rint(2.5);    // 2.0 — round half even (to the nearest even number)
Math.rint(3.5);    // 4.0 — round half even

// TRAP: round(double) vs round(float) behave differently for negative values
Math.round(-2.5);  // -2  (not -3!) — "round half up" means toward +infinity
Math.round(-3.5);  // -3

// truncate — discard the decimal part (toward zero)
(int) 2.9;   // 2  — cast truncates
(int) -2.9;  // -2 — always toward zero, not downward
Math.floor(-2.9); // -3.0 — downward (more negative)

Powers, Roots, and Logarithms #

// Powers
Math.pow(2, 10);     // 1024.0 — 2^10
Math.pow(9, 0.5);    // 3.0    — square root of 9
Math.pow(27, 1.0/3); // 3.0    — cube root of 27 (CAUTION: floating-point precision)

// Square and cube roots
Math.sqrt(16);       // 4.0
Math.cbrt(27);       // 3.0    — more accurate than Math.pow(x, 1.0/3)

// Logarithms
Math.log(Math.E);    // 1.0    — natural logarithm (ln)
Math.log(1);         // 0.0
Math.log(0);         // -Infinity
Math.log(-1);        // NaN

Math.log10(1000);    // 3.0    — base-10 logarithm
Math.log10(1);       // 0.0

// Any-base log — no direct method, use the change of base formula
double logBase2 = (n) -> Math.log(n) / Math.log(2);
// logBase2(8) = 3.0

// exp() — e^x
Math.exp(1);         // 2.718... (the value of e)
Math.exp(0);         // 1.0

// hypot() — √(x² + y²) — safer than Math.sqrt(x*x + y*y) (avoids overflow)
Math.hypot(3, 4);    // 5.0

Trigonometry #

// All trigonometric functions accept and return radians

// Constants
double PI = Math.PI;   // 3.141592653589793
double E = Math.E;     // 2.718281828459045

// Degree ↔ radian conversion
double radians = Math.toRadians(90);  // π/2 ≈ 1.5707963...
double degrees = Math.toDegrees(Math.PI); // 180.0

// Basic trigonometric functions
Math.sin(Math.toRadians(30));   // 0.5
Math.cos(Math.toRadians(60));   // 0.5
Math.tan(Math.toRadians(45));   // 1.0 (almost, because of floating point)

// Inverses
Math.asin(0.5);   // 0.5236... radians (30°)
Math.acos(0.5);   // 1.0472... radians (60°)
Math.atan(1.0);   // 0.7854... radians (45°)

// atan2 — the angle from the origin to (x, y), considering the quadrant
Math.atan2(1, 1);    //  0.7854... radians ( 45°)
Math.atan2(1, -1);   //  2.3562... radians (135°)
Math.atan2(-1, -1);  // -2.3562... radians (-135°, or 225°)

// Hyperbolic
Math.sinh(1);   // 1.1752...
Math.cosh(1);   // 1.5431...
Math.tanh(1);   // 0.7616...

Safe Integer Arithmetic #

Integer overflow is a bug that often goes unnoticed because Java doesn’t throw an exception — the result just silently “wraps around”.

// ✗ TRAP: silent integer overflow
int a = Integer.MAX_VALUE; // 2147483647
int b = a + 1;             // -2147483648 — OVERFLOW! not an exception!
System.out.println(b);     // -2147483648

int result = 100000 * 100000; // OVERFLOW: 10_000_000_000 doesn't fit in an int
System.out.println(result);   // 1410065408 — wrong!

// ✓ CORRECT: use the Math.*Exact() methods for overflow detection (Java 8+)
try {
    int safe = Math.addExact(Integer.MAX_VALUE, 1); // throws ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("Overflow detected: " + e.getMessage());
}

// All *Exact operations are available:
Math.addExact(a, b);        // a + b, throws on overflow
Math.subtractExact(a, b);   // a - b, throws on overflow
Math.multiplyExact(a, b);   // a * b, throws on overflow
Math.incrementExact(a);     // a + 1, throws on overflow
Math.decrementExact(a);     // a - 1, throws on overflow
Math.negateExact(a);        // -a, throws on overflow (specifically MIN_VALUE)
Math.toIntExact(longVal);   // casts long to int, throws if it doesn't fit

// ✓ ALTERNATIVE: use long if the value can be large
long product = (long) 100000 * 100000; // 10000000000L — safe
System.out.println(product);           // 10000000000

// Manual overflow detection for critical code
public static boolean willOverflow(int a, int b) {
    return ((long) a + b) != (int) ((long) a + b);
}

Floating-Point Arithmetic — Traps to Watch Out For #

// ✗ CLASSIC TRAP: floating point can't represent all decimals precisely
double a = 0.1 + 0.2;
System.out.println(a);        // 0.30000000000000004 — NOT 0.3!
System.out.println(a == 0.3); // false!

// ✗ ANTI-PATTERN: comparing doubles with ==
if (0.1 + 0.2 == 0.3) {   // DON'T do this!
    System.out.println("equal");
}

// ✓ CORRECT: compare with an epsilon (tolerance)
double epsilon = 1e-10;
if (Math.abs((0.1 + 0.2) - 0.3) < epsilon) {
    System.out.println("close enough");
}

// ✓ BETTER: for money and precision calculations — use BigDecimal

// Special floating-point values
double posInf = Double.POSITIVE_INFINITY; // 1.0 / 0.0
double negInf = Double.NEGATIVE_INFINITY; // -1.0 / 0.0
double nan = Double.NaN;                  // 0.0 / 0.0 or Math.sqrt(-1)

// Checking special values
Double.isInfinite(posInf);    // true
Double.isNaN(nan);            // true
Double.isFinite(3.14);        // true (Java 8+)

// NaN is not equal to itself!
System.out.println(nan == nan);        // false — the only value that isn't == itself
System.out.println(Double.isNaN(nan)); // true — the correct way

// Underflow and subnormals
double tiny = Double.MIN_VALUE;  // 4.9E-324 — the smallest representable positive value
double tooSmall = tiny / 2;  // 0.0 — underflows to zero

BigDecimal — Precision Calculations for Finance #

For calculations involving money, prices, interest, or any value that needs decimal precision, always use BigDecimal, not double or float.

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.math.MathContext;

public class BigDecimalDemo {

    public void demo() {
        // ✗ ANTI-PATTERN: money as a double
        double priceDouble = 1.10;
        double taxDouble = priceDouble * 0.1;
        System.out.println("Tax (double): " + taxDouble); // 0.11000000000000001!

        // ✓ CORRECT: money as BigDecimal
        // IMPORTANT: always use the String constructor, NOT the double constructor!
        BigDecimal price = new BigDecimal("1.10");           // ✓ precise: "1.10"
        BigDecimal wrongPrice = new BigDecimal(1.10);        // ✗ imprecise: 1.0999999...
        BigDecimal valueOfPrice = BigDecimal.valueOf(1.10);  // ✓ precise (converts via String)

        // Arithmetic operations
        BigDecimal discount = new BigDecimal("0.10");
        BigDecimal total = price.add(new BigDecimal("2.50"));
        BigDecimal afterDiscount = total.subtract(total.multiply(discount));
        BigDecimal perUnit = total.divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);

        System.out.println("Total: " + total);              // 3.60
        System.out.println("After discount: " + afterDiscount); // 3.240
        System.out.println("Per unit: " + perUnit);         // 1.20

        // Division producing a non-terminating decimal REQUIRES an explicit scale!
        // ✗ ANTI-PATTERN: division without a scale — throws ArithmeticException
        // new BigDecimal("1").divide(new BigDecimal("3")); // Exception!

        // ✓ CORRECT: specify the scale (number of decimals) and rounding mode
        BigDecimal third = new BigDecimal("1").divide(
            new BigDecimal("3"),
            10,              // 10 decimal digits
            RoundingMode.HALF_UP
        );
        System.out.println(third); // 0.3333333333

        // RoundingMode — choose based on the business context
        BigDecimal value = new BigDecimal("2.555");
        System.out.println(value.setScale(2, RoundingMode.HALF_UP));   // 2.56 — common
        System.out.println(value.setScale(2, RoundingMode.HALF_DOWN)); // 2.55
        System.out.println(value.setScale(2, RoundingMode.HALF_EVEN)); // 2.56 (banker's rounding)
        System.out.println(value.setScale(2, RoundingMode.CEILING));   // 2.56 — always up
        System.out.println(value.setScale(2, RoundingMode.FLOOR));     // 2.55 — always down
        System.out.println(value.setScale(2, RoundingMode.UP));        // 2.56 — away from zero
        System.out.println(value.setScale(2, RoundingMode.DOWN));      // 2.55 — toward zero

        // BigDecimal comparison — DON'T use equals() to compare values!
        BigDecimal a = new BigDecimal("2.0");
        BigDecimal b = new BigDecimal("2.00");
        System.out.println(a.equals(b));        // false! different scales (1 vs 2)
        System.out.println(a.compareTo(b) == 0); // true — compares mathematical values

        // ✓ The correct pattern for comparisons
        boolean equal = a.compareTo(b) == 0;          // values equal
        boolean greater = a.compareTo(b) > 0;     // a > b
        boolean less = a.compareTo(b) < 0;     // a < b

        // Other operations
        BigDecimal absolute = new BigDecimal("-5.5").abs();     // 5.5
        BigDecimal power = new BigDecimal("2").pow(10);      // 1024
        BigDecimal max = a.max(b);
        BigDecimal min = a.min(b);

        // Extracting values
        int intVal = value.intValue();             // truncates to int
        long longVal = value.longValue();          // truncates to long
        double doubleVal = value.doubleValue();    // converts to double (precision may be lost)
        String strVal = value.toPlainString();     // "2.555" (not scientific notation)
        String strValSci = value.toString();       // could be "2.555" or "2.555E+0"

        // Useful constants
        BigDecimal zero = BigDecimal.ZERO;
        BigDecimal one = BigDecimal.ONE;
        BigDecimal ten = BigDecimal.TEN;
    }
}

A Financial Calculation Example #

public class FinancialCalculations {

    private static final BigDecimal HUNDRED = new BigDecimal("100");

    // Calculate the total price with tax
    public BigDecimal calculateTotal(BigDecimal price, BigDecimal taxPercent) {
        BigDecimal tax = price.multiply(taxPercent)
            .divide(HUNDRED, 2, RoundingMode.HALF_UP);
        return price.add(tax).setScale(2, RoundingMode.HALF_UP);
    }

    // Calculate an installment (simplified)
    public BigDecimal calculateInstallment(BigDecimal principal, BigDecimal annualRatePercent, int months) {
        // r = monthly interest rate
        BigDecimal r = annualRatePercent.divide(
            HUNDRED.multiply(new BigDecimal("12")),
            10, RoundingMode.HALF_UP
        );

        // Installment = P * r * (1+r)^n / ((1+r)^n - 1)
        BigDecimal one = BigDecimal.ONE;
        BigDecimal one_plus_r = one.add(r);
        BigDecimal power = one_plus_r.pow(months);

        return principal.multiply(r).multiply(power)
            .divide(power.subtract(one), 2, RoundingMode.HALF_UP);
    }

    // Compare prices correctly
    public boolean isMoreExpensive(BigDecimal price1, BigDecimal price2) {
        return price1.compareTo(price2) > 0;
    }
}

BigInteger — Arbitrarily Large Integers #

BigInteger is used when the integer value exceeds the capacity of long (more than ±9.2 × 10¹⁸) or when you need cryptographic operations.

import java.math.BigInteger;

public class BigIntegerDemo {

    public void demo() {
        // Creating BigIntegers
        BigInteger a = new BigInteger("123456789012345678901234567890");
        BigInteger b = BigInteger.valueOf(42);
        BigInteger twoPow100 = BigInteger.TWO.pow(100); // 2^100

        // Arithmetic operations — all return a new BigInteger (immutable)
        BigInteger sum = a.add(b);
        BigInteger difference = a.subtract(b);
        BigInteger product = a.multiply(b);
        BigInteger[] quotientRemainder = a.divideAndRemainder(b); // [quotient, remainder]
        BigInteger quotient = a.divide(b);
        BigInteger remainder = a.mod(b);
        BigInteger power = b.pow(20);                  // 42^20

        // Bitwise operations
        BigInteger and = a.and(b);
        BigInteger or = a.or(b);
        BigInteger xor = a.xor(b);
        BigInteger shiftLeft = a.shiftLeft(10);          // a * 2^10
        BigInteger shiftRight = a.shiftRight(10);        // a / 2^10

        // Math functions
        BigInteger absolute = new BigInteger("-42").abs(); // 42
        BigInteger max = a.max(b);
        BigInteger min = a.min(b);
        BigInteger gcd = a.gcd(b);                       // greatest common divisor

        // Prime numbers
        boolean probablyPrime = a.isProbablePrime(100);     // Miller-Rabin with 100 iterations
        BigInteger nextPrime = a.nextProbablePrime();

        // Comparison
        int cmp = a.compareTo(b);   // < 0, == 0, or > 0
        boolean equal = a.equals(b); // safe for BigInteger (unlike BigDecimal!)

        // Conversion
        long longVal = b.longValue();        // throws ArithmeticException if it doesn't fit: longValueExact()
        int intVal = b.intValueExact();      // Java 8+ — throws if it doesn't fit
        byte[] bytes = a.toByteArray();      // byte representation (two's complement)
        String hex = a.toString(16);         // hex representation
        String binary = a.toString(2);        // binary representation

        // Constants
        BigInteger zero = BigInteger.ZERO;
        BigInteger one = BigInteger.ONE;
        BigInteger two = BigInteger.TWO;
        BigInteger ten = BigInteger.TEN;
    }

    // Factorial — a real-world example of needing BigInteger
    public BigInteger factorial(int n) {
        BigInteger result = BigInteger.ONE;
        for (int i = 2; i <= n; i++) {
            result = result.multiply(BigInteger.valueOf(i));
        }
        return result;
    }
    // factorial(100) = 93326215443944152681699238856266700490715968264381...
    // (158 digits — doesn't fit in a long!)

    // Fibonacci with BigInteger
    public BigInteger fibonacci(int n) {
        BigInteger a = BigInteger.ZERO;
        BigInteger b = BigInteger.ONE;
        for (int i = 0; i < n; i++) {
            BigInteger temp = a.add(b);
            a = b;
            b = temp;
        }
        return a;
    }
}

Random — Random Numbers #

Java provides several classes for generating random numbers with different characteristics.

java.util.Random #

import java.util.Random;

public class RandomDemo {

    public void demo() {
        Random rng = new Random();

        // Random integers
        int random = rng.nextInt();                    // the whole int range
        int randomBounded = rng.nextInt(100);            // 0 to 99 (exclusive)
        int randomRange = rng.nextInt(50, 100);        // 50 to 99 (Java 17+)

        // Other types
        long randomLong = rng.nextLong();
        long randomLongRange = rng.nextLong(1000L);    // 0 to 999 (Java 17+)
        double randomDouble = rng.nextDouble();        // 0.0 to < 1.0
        double randomDoubleRange = rng.nextDouble(1.0, 10.0); // Java 17+
        boolean randomBool = rng.nextBoolean();

        // Gaussian distribution (normal distribution)
        double gaussian = rng.nextGaussian();        // mean=0, std=1

        // Streams of random numbers (Java 8+)
        int[] randomArray = rng.ints(10, 1, 101)     // 10 numbers, 1-100
            .toArray();

        double[] randomDoubles = rng.doubles(5, 0.0, 1.0) // 5 doubles, 0-1
            .toArray();

        // Deterministic seed — for testing and reproducibility
        Random seeded = new Random(12345L);          // fixed seed
        int value1 = seeded.nextInt(100);            // ALWAYS produces the same value
        int value2 = seeded.nextInt(100);            // a deterministic sequence

        // Random element from an array
        int[] data = {10, 20, 30, 40, 50};
        int randomElement = data[rng.nextInt(data.length)];

        // Shuffle an array
        Integer[] arr = {1, 2, 3, 4, 5};
        java.util.List<Integer> list = java.util.Arrays.asList(arr);
        java.util.Collections.shuffle(list, rng);
    }
}

ThreadLocalRandom — Faster for Multi-threading #

import java.util.concurrent.ThreadLocalRandom;

public class ThreadLocalRandomDemo {

    // ✗ ANTI-PATTERN: sharing one Random instance across many threads — contention!
    private static final Random SHARED_RANDOM = new Random();

    // ✓ CORRECT: ThreadLocalRandom — one instance per thread, no contention
    public void demo() {
        // No need to store an instance — access via the static method
        int random = ThreadLocalRandom.current().nextInt(1, 101); // 1 to 100

        // Inside parallel streams — ThreadLocalRandom is used automatically
        int[] results = ThreadLocalRandom.current().ints(1000, 0, 100).toArray();
    }
}

SecureRandom — Cryptographically Secure Random Numbers #

For security needs (tokens, passwords, cryptographic keys), plain Random isn’t enough because its output can be predicted if the seed is known.

import java.security.SecureRandom;

public class SecureRandomDemo {

    // ✗ ANTI-PATTERN: Random for security tokens — predictable!
    public String createUnsafeToken() {
        Random rng = new Random();
        StringBuilder token = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            token.append(Integer.toHexString(rng.nextInt(16)));
        }
        return token.toString(); // NOT SAFE
    }

    // ✓ CORRECT: SecureRandom for security tokens
    private static final SecureRandom SECURE_RNG = new SecureRandom();
    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();

    public String createSafeToken(int byteLength) {
        byte[] bytes = new byte[byteLength];
        SECURE_RNG.nextBytes(bytes);

        // Convert to a hex string
        StringBuilder sb = new StringBuilder(byteLength * 2);
        for (byte b : bytes) {
            sb.append(HEX_CHARS[(b >> 4) & 0xF]);
            sb.append(HEX_CHARS[b & 0xF]);
        }
        return sb.toString();
    }

    // Or use Base64 for shorter tokens
    public String createBase64Token(int byteLength) {
        byte[] bytes = new byte[byteLength];
        SECURE_RNG.nextBytes(bytes);
        return java.util.Base64.getUrlEncoder()
            .withoutPadding()
            .encodeToString(bytes);
    }

    // Random numbers in a range (safe)
    public int safeNumberInRange(int min, int max) {
        // nextInt(bound) from SecureRandom avoids modulo bias
        return min + SECURE_RNG.nextInt(max - min);
    }
}
SecureRandom is far slower than plain Random because it uses OS entropy sources (like /dev/urandom on Linux). Use it only for cryptographic needs — session tokens, password resets, API keys. For simulations, games, or test data, Random or ThreadLocalRandom is sufficient.

Numeric Conversion #

Between Primitive Types #

// Widening — automatic, loses no data
byte b = 100;
short s = b;    // automatic
int i = s;      // automatic
long l = i;     // automatic
float f = l;    // automatic — but CAUTION: float only has a 23-bit mantissa
double d = f;   // automatic

// Narrowing — must be explicit with a cast, can lose data
double doubleValue = 9.99;
int intValue = (int) doubleValue;  // 9 — the decimal part is discarded (truncated, not rounded!)
long longValue = 300L;
byte byteValue = (byte) longValue; // 44 — overflow, wraps around!

// The safe way to narrow
try {
    int exact = Math.toIntExact(longValue);  // throws if it doesn't fit
} catch (ArithmeticException e) {
    System.out.println("The value doesn't fit in an int");
}

// String to numeric (already covered in Strings, repeated for completeness)
int fromString = Integer.parseInt("42");
double fromStringD = Double.parseDouble("3.14");
BigDecimal fromStringBD = new BigDecimal("1234.56");

Bit and Byte Representations #

// Bit representations of float/double
int floatBits = Float.floatToIntBits(3.14f);          // bit pattern as an int
int floatRawBits = Float.floatToRawIntBits(3.14f);    // without NaN normalization
float back = Float.intBitsToFloat(floatBits);      // 3.14

long doubleBits = Double.doubleToLongBits(3.14);
double backD = Double.longBitsToDouble(doubleBits);

// Integer representations in various bases
String hex = Integer.toHexString(255);     // "ff"
String binary = Integer.toBinaryString(10); // "1010"
String octal = Integer.toOctalString(8);  // "10"

// Parsing from various bases
int fromHex = Integer.parseInt("FF", 16); // 255
int fromBinary = Integer.parseInt("1010", 2); // 10

// Low-level bit operations
Integer.bitCount(255);          // 8 — the number of 1 bits
Integer.highestOneBit(100);     // 64 — the highest active bit
Integer.lowestOneBit(100);      // 4 — the lowest active bit
Integer.numberOfLeadingZeros(1); // 31
Integer.numberOfTrailingZeros(8); // 3
Integer.reverse(1);              // 2147483648 — reverses all bits
Integer.reverseBytes(0x12345678); // 0x78563412

When to Use Which Numeric Type #

double / float
  ✓ Scientific and engineering calculations
  ✓ Graphical coordinates and geometry
  ✓ Statistics and machine learning
  ✗ DON'T use for money, prices, or financial values
  ✗ DON'T use for values compared with ==

int / long
  ✓ Counters, indices, IDs, timestamps
  ✓ Bit manipulation
  ✗ DON'T use if the value can overflow — use *Exact() or BigInteger

BigDecimal
  ✓ Money, prices, financial values
  ✓ Calculations needing exact decimal precision
  ✓ Tax rates, interest, discounts
  ✗ DON'T create from a double — always from a String or valueOf()
  ✗ DON'T use equals() for value comparison — use compareTo()

BigInteger
  ✓ Numbers exceeding Long.MAX_VALUE
  ✓ Cryptography (RSA, DH key generation)
  ✓ Factorials, large Fibonacci, combinatorics
  ✗ Slower and more memory-hungry than int/long

Random vs ThreadLocalRandom vs SecureRandom
  Random          → single-threaded, deterministic seeds for testing
  ThreadLocalRandom → multi-threaded, faster than Random
  SecureRandom    → cryptography — tokens, passwords, keys

Summary #

  • Integer overflow is silent — Java doesn’t throw an exception when int or long overflows. Use Math.addExact(), Math.multiplyExact(), and similar for overflow detection in critical calculations.
  • double is inaccurate for money0.1 + 0.2 != 0.3 in floating point. Use BigDecimal for all financial calculations without exception.
  • Create BigDecimal from a String, not from a doublenew BigDecimal("0.1") is precise, new BigDecimal(0.1) inherits floating-point inaccuracy.
  • Compare BigDecimal with compareTo(), not equals()new BigDecimal("2.0").equals(new BigDecimal("2.00")) returns false because the scales differ.
  • Math.round() with negative .5 values behaves unintuitively-2.5 rounds to -2, not -3, because “round half up” means toward positive infinity. Use RoundingMode.HALF_UP with BigDecimal for more predictable results.
  • ThreadLocalRandom for multi-threading — more efficient than sharing one Random instance, which causes contention in concurrent environments.
  • SecureRandom only for cryptography — far slower than Random, use it only for tokens, passwords, and keys that need cryptographic unpredictability.
  • NaN != NaN is the only value in Java that isn’t equal to itself. Always use Double.isNaN() to check for NaN, not ==.

← Previous: IO
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact