BigDecimal y BigInteger en Java

1. Información general

En este tutorial, demostraremos BigDecimal y las clases BigInteger .

Describiremos los dos tipos de datos, sus características y sus escenarios de uso. También cubriremos brevemente las diversas operaciones que utilizan las dos clases.

2. BigDecimal

BigDecimal representa un número decimal con signo de precisión arbitraria inmutable . Está formado por dos partes:

  • Valor sin escala: un entero de precisión arbitrario
  • Escala: un entero de 32 bits que representa el número de dígitos a la derecha del punto decimal.

Por ejemplo, BigDecimal 3.14 tiene el valor sin escala de 314 y la escala de 2.

Usamos BigDecimal para aritmética de alta precisión. También lo usamos para cálculos que requieren control sobre la escala y el comportamiento de redondeo . Un ejemplo de ello son los cálculos que involucran transacciones financieras.

Podemos crear un objeto BigDecimal de String , character array, int , long y BigInteger :

@Test public void whenBigDecimalCreated_thenValueMatches() { BigDecimal bdFromString = new BigDecimal("0.1"); BigDecimal bdFromCharArray = new BigDecimal(new char[] {'3','.','1','6','1','5'}); BigDecimal bdlFromInt = new BigDecimal(42); BigDecimal bdFromLong = new BigDecimal(123412345678901L); BigInteger bigInteger = BigInteger.probablePrime(100, new Random()); BigDecimal bdFromBigInteger = new BigDecimal(bigInteger); assertEquals("0.1",bdFromString.toString()); assertEquals("3.1615",bdFromCharArray.toString()); assertEquals("42",bdlFromInt.toString()); assertEquals("123412345678901",bdFromLong.toString()); assertEquals(bigInteger.toString(),bdFromBigInteger.toString()); }

También podemos crear BigDecimal desde double :

@Test public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() { BigDecimal bdFromDouble = new BigDecimal(0.1d); assertNotEquals("0.1", bdFromDouble.toString()); }

Sin embargo, el resultado, en este caso, es diferente al esperado (es decir, 0,1). Esto es porque:

  • el constructor doble hace una traducción exacta
  • 0.1 no tiene una representación exacta en doble

Por lo tanto, deberíamos usar el constructor S tring en lugar del constructor doble .

Además, podemos convertir double y long a BigInteger usando el método valueOf estático:

@Test public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() { BigDecimal bdFromLong1 = BigDecimal.valueOf(123412345678901L); BigDecimal bdFromLong2 = BigDecimal.valueOf(123412345678901L, 2); BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d); assertEquals("123412345678901", bdFromLong1.toString()); assertEquals("1234123456789.01", bdFromLong2.toString()); assertEquals("0.1", bdFromDouble.toString()); }

Este método convierte el doble en su representación de cadena antes de convertir a BigDecimal . Además, puede reutilizar instancias de objetos.

Por lo tanto, deberíamos usar el método valueOf en lugar de los constructores .

3. Operaciones en BigDecimal

Al igual que las otras clases de números ( Integer , Long , Double , etc.), BigDecimal proporciona operaciones para operaciones aritméticas y de comparación. También proporciona operaciones para manipulación de escalas, redondeo y conversión de formato.

No sobrecarga los operadores aritméticos (+, -, /, *) o lógicos (>. <Etc). En su lugar, usamos los métodos correspondientes: sumar , restar , multiplicar , dividir y comparar.

BigDecimal tiene métodos para extraer varios atributos, como precisión, escala y signo :

@Test public void whenGettingAttributes_thenExpectedResult() { BigDecimal bd = new BigDecimal("-12345.6789"); assertEquals(9, bd.precision()); assertEquals(4, bd.scale()); assertEquals(-1, bd.signum()); }

Comparamos el valor de dos BigDecimals usando el método compareTo :

@Test public void whenComparingBigDecimals_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); BigDecimal bd3 = new BigDecimal("2.0"); assertTrue(bd1.compareTo(bd3)  0); assertTrue(bd1.compareTo(bd2) == 0); assertTrue(bd1.compareTo(bd3) = 0); assertTrue(bd1.compareTo(bd3) != 0); }

Este método ignora la escala al comparar.

Por otro lado, el método equals considera dos objetos BigDecimal como iguales solo si son iguales en valor y escala . Por lo tanto, BigDecimals 1.0 y 1.00 no son iguales cuando se comparan con este método.

@Test public void whenEqualsCalled_thenSizeAndScaleMatched() { BigDecimal bd1 = new BigDecimal("1.0"); BigDecimal bd2 = new BigDecimal("1.00"); assertFalse(bd1.equals(bd2)); }

Realizamos operaciones aritméticas llamando a los métodos correspondientes :

@Test public void whenPerformingArithmetic_thenExpectedResult() { BigDecimal bd1 = new BigDecimal("4.0"); BigDecimal bd2 = new BigDecimal("2.0"); BigDecimal sum = bd1.add(bd2); BigDecimal difference = bd1.subtract(bd2); BigDecimal quotient = bd1.divide(bd2); BigDecimal product = bd1.multiply(bd2); assertTrue(sum.compareTo(new BigDecimal("6.0")) == 0); assertTrue(difference.compareTo(new BigDecimal("2.0")) == 0); assertTrue(quotient.compareTo(new BigDecimal("2.0")) == 0); assertTrue(product.compareTo(new BigDecimal("8.0")) == 0); }

Dado que BigDecimal es inmutable, estas operaciones no modifican los objetos existentes. Más bien, devuelven nuevos objetos.

4. Redondeo y BigDecimal

Al redondear un número, lo reemplazamos por otro que tenga una representación más corta, simple y significativa . Por ejemplo, redondeamos $ 24,784917 a $ 24,78 ya que no tenemos centavos fraccionarios.

El modo de precisión y redondeo que se utilizará varía según el cálculo. Por ejemplo, las declaraciones de impuestos federales de EE . UU . Especifican redondear a montos en dólares enteros mediante HALF_UP .

Hay dos clases que controlan el comportamiento de redondeo: RoundingMode y MathContext .

La enumeración RoundingMode proporciona ocho modos de redondeo:

  • TECHO - redondea hacia el infinito positivo
  • PISO - redondea hacia infinito negativo
  • ARRIBA: redondea desde cero
  • ABAJO - redondea hacia cero
  • HALF_UP: redondea hacia el "vecino más cercano" a menos que ambos vecinos sean equidistantes, en cuyo caso redondea hacia arriba
  • HALF_DOWN: redondea hacia el "vecino más cercano" a menos que ambos vecinos sean equidistantes, en cuyo caso redondea hacia abajo
  • HALF_EVEN: redondea hacia el "vecino más cercano" a menos que ambos vecinos sean equidistantes, en cuyo caso, redondea hacia el vecino par
  • INNECESARIO: no es necesario redondear y se lanza ArithmeticException si no es posible obtener un resultado exacto

El modo de redondeo HALF_EVEN minimiza el sesgo debido a las operaciones de redondeo. Se utiliza con frecuencia. También se conoce como redondeo bancario .

MathContext encapsula tanto el modo de precisión como el de redondeo . Hay algunos MathContexts predefinidos:

  • DECIMAL32 - precisión de 7 dígitos y un modo de redondeo de HALF_EVEN
  • DECIMAL64 - precisión de 16 dígitos y un modo de redondeo de HALF_EVEN
  • DECIMAL128 - precisión de 34 dígitos y un modo de redondeo de HALF_EVEN
  • ILIMITADO : aritmética de precisión ilimitada

Usando esta clase, podemos redondear un número BigDecimal usando la precisión especificada y el comportamiento de redondeo:

@Test public void whenRoundingDecimal_thenExpectedResult() { BigDecimal bd = new BigDecimal("2.5"); // Round to 1 digit using HALF_EVEN BigDecimal rounded = bd .round(new MathContext(1, RoundingMode.HALF_EVEN)); assertEquals("2", rounded.toString()); }

Ahora, examinemos el concepto de redondeo usando un cálculo de muestra.

Escribamos un método para calcular la cantidad total a pagar por un artículo dada una cantidad y precio unitario. Apliquemos también una tasa de descuento y una tasa de impuesto sobre las ventas. Redondeamos el resultado final a centavos usando el método setScale :

public static BigDecimal calculateTotalAmount(BigDecimal quantity, BigDecimal unitPrice, BigDecimal discountRate, BigDecimal taxRate) { BigDecimal amount = quantity.multiply(unitPrice); BigDecimal discount = amount.multiply(discountRate); BigDecimal discountedAmount = amount.subtract(discount); BigDecimal tax = discountedAmount.multiply(taxRate); BigDecimal total = discountedAmount.add(tax); // round to 2 decimal places using HALF_EVEN BigDecimal roundedTotal = total.setScale(2, RoundingMode.HALF_EVEN); return roundedTotal; }

Ahora, escriba una prueba unitaria para este método:

@Test public void givenPurchaseTxn_whenCalculatingTotalAmount_thenExpectedResult() { BigDecimal quantity = new BigDecimal("4.5"); BigDecimal unitPrice = new BigDecimal("2.69"); BigDecimal discountRate = new BigDecimal("0.10"); BigDecimal taxRate = new BigDecimal("0.0725"); BigDecimal amountToBePaid = BigDecimalDemo .calculateTotalAmount(quantity, unitPrice, discountRate, taxRate); assertEquals("11.68", amountToBePaid.toString()); }

5. BigInteger

BigInteger representa enteros inmutables de precisión arbitraria . Es similar a los tipos de enteros primitivos pero permite valores grandes arbitrarios.

Se usa cuando los números enteros involucrados son mayores que el límite del tipo largo . Por ejemplo, el factorial de 50 es 30414093201713378043612608166064768844377641568960512000000000000. Este valor es demasiado grande para que lo maneje un tipo de datos int o long . Solo se puede almacenar en una variable BigInteger .

Se usa ampliamente en aplicaciones de seguridad y criptografía.

Podemos crear BigInteger a partir de una matriz de bytes o String :

@Test public void whenBigIntegerCreatedFromConstructor_thenExpectedResult() { BigInteger biFromString = new BigInteger("1234567890987654321"); BigInteger biFromByteArray = new BigInteger( new byte[] { 64, 64, 64, 64, 64, 64 }); BigInteger biFromSignMagnitude = new BigInteger(-1, new byte[] { 64, 64, 64, 64, 64, 64 }); assertEquals("1234567890987654321", biFromString.toString()); assertEquals("70644700037184", biFromByteArray.toString()); assertEquals("-70644700037184", biFromSignMagnitude.toString()); }

Además, podemos convertir un long a BigInteger usando el método estático valueOf :

@Test public void whenLongConvertedToBigInteger_thenValueMatches() { BigInteger bi = BigInteger.valueOf(2305843009213693951L); assertEquals("2305843009213693951", bi.toString()); }

6. Operaciones en BigInteger

Similar a int y long , BigInteger implementa todas las operaciones aritméticas y lógicas. Pero no sobrecarga a los operadores.

También implementa los métodos correspondientes de la clase Math : abs , min , max , pow , signum .

Comparamos el valor de dos BigIntegers usando el método compareTo :

@Test public void givenBigIntegers_whentCompared_thenExpectedResult() { BigInteger i = new BigInteger("123456789012345678901234567890"); BigInteger j = new BigInteger("123456789012345678901234567891"); BigInteger k = new BigInteger("123456789012345678901234567892"); assertTrue(i.compareTo(i) == 0); assertTrue(j.compareTo(i) > 0); assertTrue(j.compareTo(k) < 0); }

Realizamos operaciones aritméticas llamando a los métodos correspondientes:

@Test public void givenBigIntegers_whenPerformingArithmetic_thenExpectedResult() { BigInteger i = new BigInteger("4"); BigInteger j = new BigInteger("2"); BigInteger sum = i.add(j); BigInteger difference = i.subtract(j); BigInteger quotient = i.divide(j); BigInteger product = i.multiply(j); assertEquals(new BigInteger("6"), sum); assertEquals(new BigInteger("2"), difference); assertEquals(new BigInteger("2"), quotient); assertEquals(new BigInteger("8"), product); }

Como BigInteger es inmutable, estas operaciones no modifican los objetos existentes. A diferencia de int y long , estas operaciones no se desbordan.

BigInteger tiene operaciones de bits similares a int y long . Pero, necesitamos usar los métodos en lugar de operadores:

@Test public void givenBigIntegers_whenPerformingBitOperations_thenExpectedResult() { BigInteger i = new BigInteger("17"); BigInteger j = new BigInteger("7"); BigInteger and = i.and(j); BigInteger or = i.or(j); BigInteger not = j.not(); BigInteger xor = i.xor(j); BigInteger andNot = i.andNot(j); BigInteger shiftLeft = i.shiftLeft(1); BigInteger shiftRight = i.shiftRight(1); assertEquals(new BigInteger("1"), and); assertEquals(new BigInteger("23"), or); assertEquals(new BigInteger("-8"), not); assertEquals(new BigInteger("22"), xor); assertEquals(new BigInteger("16"), andNot); assertEquals(new BigInteger("34"), shiftLeft); assertEquals(new BigInteger("8"), shiftRight); }

Tiene métodos adicionales de manipulación de bits :

@Test public void givenBigIntegers_whenPerformingBitManipulations_thenExpectedResult() { BigInteger i = new BigInteger("1018"); int bitCount = i.bitCount(); int bitLength = i.bitLength(); int getLowestSetBit = i.getLowestSetBit(); boolean testBit3 = i.testBit(3); BigInteger setBit12 = i.setBit(12); BigInteger flipBit0 = i.flipBit(0); BigInteger clearBit3 = i.clearBit(3); assertEquals(8, bitCount); assertEquals(10, bitLength); assertEquals(1, getLowestSetBit); assertEquals(true, testBit3); assertEquals(new BigInteger("5114"), setBit12); assertEquals(new BigInteger("1019"), flipBit0); assertEquals(new BigInteger("1010"), clearBit3); }

BigInteger proporciona métodos para el cálculo GCD y la aritmética modular :

@Test public void givenBigIntegers_whenModularCalculation_thenExpectedResult() { BigInteger i = new BigInteger("31"); BigInteger j = new BigInteger("24"); BigInteger k = new BigInteger("16"); BigInteger gcd = j.gcd(k); BigInteger multiplyAndmod = j.multiply(k).mod(i); BigInteger modInverse = j.modInverse(i); BigInteger modPow = j.modPow(k, i); assertEquals(new BigInteger("8"), gcd); assertEquals(new BigInteger("12"), multiplyAndmod); assertEquals(new BigInteger("22"), modInverse); assertEquals(new BigInteger("7"), modPow); }

También tiene métodos relacionados con la generación principal y las pruebas de primalidad :

@Test public void givenBigIntegers_whenPrimeOperations_thenExpectedResult() { BigInteger i = BigInteger.probablePrime(100, new Random()); boolean isProbablePrime = i.isProbablePrime(1000); assertEquals(true, isProbablePrime); }

7. Conclusión

En este tutorial rápido, exploramos las clases BigDecimal y BigInteger. Son útiles para cálculos numéricos avanzados donde los tipos de enteros primitivos no son suficientes.

Como de costumbre, el código fuente completo se puede encontrar en GitHub.