floating-point - ventajas - tipo de cambio mixto




¿Por qué no usar doble o flotar para representar la moneda? (10)

Como se dijo anteriormente, "representar el dinero como un doble o flotante probablemente se verá bien al principio, ya que el software redondea los pequeños errores, pero a medida que realiza más adiciones, restas, multiplicaciones y divisiones en números inexactos, perderá más y más precisión. a medida que se acumulan los errores. Esto hace que las flotaciones y los dobles sean inadecuados para tratar con el dinero, donde se requiere una precisión perfecta para múltiplos de potencias de base 10 ".

¡Finalmente Java tiene una forma estándar de trabajar con Currency And Money!

JSR 354: API de dinero y moneda

JSR 354 proporciona una API para representar, transportar y realizar cálculos completos con dinero y moneda. Puedes descargarlo desde este enlace:

JSR 354: Descarga del API de Money and Currency

La especificación consta de las siguientes cosas:

  1. Una API para el manejo, por ejemplo, montos monetarios y monedas.
  2. APIs para soportar implementaciones intercambiables
  3. Fábricas para crear instancias de las clases de implementación.
  4. Funcionalidad para cálculos, conversión y formateo de importes monetarios.
  5. API de Java para trabajar con Money y Currencies, que se planea incluir en Java 9.
  6. Todas las clases de especificación e interfaces se encuentran en el paquete javax.money. *.

Ejemplos de ejemplo de JSR 354: API de Money and Currency:

Un ejemplo de cómo crear un Monto Monetario e imprimirlo en la consola tiene este aspecto:

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

Cuando se utiliza la API de implementación de referencia, el código necesario es mucho más simple:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

La API también admite cálculos con montos monetarios:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

MonedaUnidad y Monto Monetario

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount tiene varios métodos que permiten acceder a la moneda asignada, el monto numérico, su precisión y más:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number. 
// So we assign numberValue to a variable of type Number
Number number = numberValue;

Los montos monetarios se pueden redondear utilizando un operador de redondeo:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

Cuando se trabaja con colecciones de Montos Monetarios, hay disponibles algunos métodos útiles de utilidad para filtrar, clasificar y agrupar.

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

Operaciones monetarias personalizadas

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
  BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
  BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
  return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

Recursos:

Manejo de dinero y monedas en Java con JSR 354

Buscando en la API de Java 9 Money y Currency (JSR 354)

Ver también: JSR 354 - Moneda y dinero

Siempre me han dicho que nunca represente dinero con tipos double o float , y esta vez le pregunto: ¿por qué?

Estoy seguro de que hay una muy buena razón, simplemente no sé cuál es.


El resultado del número de punto flotante no es exacto, lo que los hace inadecuados para cualquier cálculo financiero que requiera un resultado exacto y no una aproximación. float y double están diseñados para el cálculo científico y de ingeniería, y muchas veces no producen resultados exactos, ya que el cálculo del punto flotante puede variar de JVM a JVM. Mire el ejemplo de BigDecimal y la primitiva doble que se usa para representar el valor monetario, es bastante claro que el cálculo de punto flotante puede no ser exacto y se debe usar BigDecimal para los cálculos financieros.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

Salida:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

Estoy preocupado por algunas de estas respuestas. Creo que los dobles y los flotadores tienen un lugar en los cálculos financieros. Ciertamente, al sumar y restar montos monetarios no fraccionarios no habrá pérdida de precisión al usar clases de enteros o clases de BigDecimal. Pero al realizar operaciones más complejas, a menudo terminas con resultados que van con varios o muchos decimales, sin importar cómo almacenes los números. El problema es cómo presentar el resultado.

Si su resultado está en el límite entre ser redondeado hacia arriba y hacia abajo, y ese último centavo realmente importa, probablemente debería decirle al espectador que la respuesta está casi en el medio, al mostrar más lugares decimales.

El problema con los dobles, y más con los flotadores, es cuando se usan para combinar números grandes y pequeños. En java

System.out.println(1000000.0f + 1.2f - 1000000.0f);

resultados en

1.1875

Flotantes y dobles son aproximados. Si creas un BigDecimal y pasas un flotador al constructor, verás lo que realmente es el flotante:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

Probablemente no sea así como quieres representar $ 1.01.

El problema es que la especificación IEEE no tiene una manera de representar exactamente todas las fracciones, algunas de ellas terminan como fracciones repetidas, por lo que terminas con errores de aproximación. Dado que a los contadores les gusta que las cosas salgan exactamente al centavo, y los clientes se sentirán molestos si pagan su factura y una vez que se procesa el pago, deben .01 y se les cobra una tarifa o no pueden cerrar su cuenta, es mejor usar Tipos exactos como decimal (en C #) o java.math.BigDecimal en Java.

No es que el error no sea controlable si redondeas: consulta este artículo de Peter Lawrey . Simplemente es más fácil no tener que redondear en primer lugar. La mayoría de las aplicaciones que manejan dinero no requieren muchos cálculos matemáticos, las operaciones consisten en agregar cosas o asignar cantidades a diferentes categorías. La introducción del punto flotante y el redondeo simplemente complica las cosas.


Me arriesgaré a ser votado, pero creo que la inadecuación de los números de punto flotante para los cálculos de moneda está sobrevaluada. Mientras se asegure de hacer el redondeo de los centavos correctamente y tenga suficientes dígitos significativos para trabajar para contrarrestar la falta de coincidencia de la representación decimal-binaria explicada por zneak, no habrá problema.

Las personas que calculan con moneda en Excel siempre han usado flotadores de doble precisión (no hay tipo de moneda en Excel) y todavía no he visto a nadie quejándose de errores de redondeo.

Por supuesto, tienes que estar dentro de la razón; por ejemplo, una tienda web simple probablemente nunca experimentará ningún problema con flotadores de doble precisión, pero si lo hace, por ejemplo, contabilidad o cualquier otra cosa que requiera agregar una gran cantidad de números (no restringidos), no querría tocar números de punto flotante con un pie de diez pies. polo.


Muchas de las respuestas publicadas a esta pregunta tratan sobre IEEE y los estándares que rodean la aritmética de punto flotante.

Con una formación no informática (física e ingeniería), tiendo a considerar los problemas desde una perspectiva diferente. Para mí, la razón por la que no usaría doble o flotar en un cálculo matemático es porque perdería demasiada información.

¿Cuáles son las alternativas? Hay muchos (y muchos más de los cuales no estoy enterado).

BigDecimal en Java es nativo del lenguaje Java. Apfloat es otra biblioteca de precisión arbitraria para Java.

El tipo de datos decimales en C # es la alternativa .NET de Microsoft para 28 cifras significativas.

SciPy (Scientific Python) probablemente también puede manejar cálculos financieros (no lo he intentado, pero sospecho que sí).

La biblioteca de precisión múltiple (GMP) de GNU y la biblioteca MFPR de GNU son dos recursos gratuitos y de código abierto para C y C ++.

También hay bibliotecas de precisión numérica para JavaScript (!) Y creo que PHP puede manejar cálculos financieros.

También hay soluciones propietarias (particularmente, creo, para Fortran) y de código abierto, así como para muchos lenguajes informáticos.

No soy un informático en formación. Sin embargo, tiendo a inclinarme hacia BigDecimal en Java o decimal en C #. No he probado las otras soluciones que he enumerado, pero probablemente también sean muy buenas.

Para mí, me gusta BigDecimal debido a los métodos que soporta. El decimal de C # es muy bueno, pero no he tenido la oportunidad de trabajar con él tanto como me gustaría. Hago cálculos científicos que me interesan en mi tiempo libre, y BigDecimal parece funcionar muy bien porque puedo establecer la precisión de mis números de punto flotante. ¿La desventaja de BigDecimal? Puede ser lento a veces, especialmente si está utilizando el método de división.

Para la velocidad, puede buscar en las bibliotecas gratuitas y propietarias de C, C ++ y Fortran.


Prefiero usar Integer o Long para representar la moneda. BigDecimal junks el código fuente demasiado.

Solo tienes que saber que todos tus valores están en centavos. O el valor más bajo de cualquier moneda que estés usando.


Si bien es cierto que el tipo de punto flotante puede representar solo datos aproximadamente decimales, también es cierto que si uno redondea los números a la precisión necesaria antes de presentarlos, obtiene el resultado correcto. Generalmente.

Normalmente porque el tipo doble tiene una precisión inferior a 16 cifras. Si necesita una mejor precisión no es un tipo adecuado. También se pueden acumular aproximaciones.

Debe decirse que incluso si usa la aritmética de punto fijo, todavía tiene que redondear los números, si no fuera por el hecho de que BigInteger y BigDecimal dan errores si obtiene números decimales periódicos. Así que hay una aproximación también aquí.

Por ejemplo, COBOL, utilizado históricamente para cálculos financieros, tiene una precisión máxima de 18 cifras. Así que a menudo hay un redondeo implícito.

En conclusión, en mi opinión, el doble no es adecuado principalmente por su precisión de 16 dígitos, que puede ser insuficiente, no porque sea aproximado.

Considere la siguiente salida del programa posterior. Muestra que después del redondeo doble da el mismo resultado que BigDecimal hasta la precisión 16.

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}

De Bloch, J., Effective Java, 2nd ed, Item 48:

Los tipos float y double son particularmente inadecuados para los cálculos monetarios porque es imposible representar 0.1 (o cualquier otra potencia negativa de diez) como float o double exactamente.

Por ejemplo, suponga que tiene $ 1.03 y gasta 42c. ¿Cuánto dinero te queda?

System.out.println(1.03 - .42);

imprime 0.6100000000000001 .

La forma correcta de resolver este problema es usar BigDecimal , int o long para cálculos monetarios.


Algunos ejemplos ... esto funciona (en realidad no funciona como se esperaba), en casi cualquier lenguaje de programación ... Lo he intentado con Delphi, VBScript, Visual Basic, JavaScript y ahora con Java / Android:

    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
    }

SALIDA:

round problems?: current total: 0.9999999999999999 round problems?: current total: 2.7755575615628914E-17 round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!







currency