En un simple juego de simulación de negocios (construido en Java + Slick2D), ¿la cantidad actual de dinero de un jugador debe almacenarse como una float
o una int
u otra cosa?
En mi caso de uso, la mayoría de las transacciones usarán centavos ($ 0.50, $ 1.20, etc.), y se realizarán cálculos simples de tasas de interés.
He visto personas que dicen que nunca debes usar float
para la moneda, así como personas que dicen que nunca debes usar int
para la moneda. Siento que debería usar int
y redondear cualquier cálculo de porcentaje necesario. ¿Qué debo usar?
Currency
tipo como el de Delphi, que utiliza matemática de punto fijo escalado para darle matemática decimal sin los problemas de precisión inherentes a la coma flotante?BigDecimal
para este tipo de problemas.Respuestas:
Puede usar
int
y considerar todo en centavos. $ 1.20 es solo 120 centavos. En la pantalla, coloca el decimal en el lugar al que pertenece.Los cálculos de intereses se truncarían o redondearían. Entonces
De esta manera no tienes decimales desordenados siempre pegados. Puede hacerse rico agregando el dinero no contabilizado (debido a redondeos) en su propia cuenta bancaria
fuente
round
no hay una razón real para rechazarint
, ¿ verdad ?Ok, voy a saltar
Mi consejo: es un juego. Tómatelo con calma y úsalo
double
.Aquí está mi justificación:
float
tiene un problema de precisión que aparece al agregar unidades a millones, por lo que, aunque podría ser benigno, evitaría ese tipo.double
solo comienza a tener problemas alrededor de los quintillones (mil millones de billones).int
elong
inútil porque no se sabe dónde parar con los decimalesBigDecimal
le dará precisión arbitraria pero se volverá dolorosamente lenta, a menos que sujete la precisión en algún momento. ¿Cuáles son las reglas de redondeo? ¿Cómo eliges dónde parar? ¿Vale la pena tener más brocas de precisión quedouble
? Yo creo que no.La razón por la que se usa la aritmética de punto fijo en las aplicaciones financieras es porque son deterministas. Las reglas de redondeo están perfectamente definidas, a veces por la ley, y deben aplicarse estrictamente, pero el redondeo todavía ocurre en algún momento. Cualquier argumento a favor de un tipo dado basado en la precisión es probablemente falso. Todos los tipos tienen problemas de precisión con el tipo de cálculos que va a hacer.
Ejemplos prácticos
Veo algunos comentarios alegando cosas sobre redondeo o precisión con las que no estoy de acuerdo. Aquí hay algunos ejemplos adicionales para ilustrar lo que quiero decir.
Almacenamiento : si su unidad base es el centavo, probablemente desee redondear al centavo más cercano al almacenar valores:
Obtendrá absolutamente ningún problema de redondeo al adoptar este método que tampoco tendría con un tipo entero.
Recuperación : todos los cálculos se pueden hacer directamente en los valores dobles, sin conversiones:
Tenga en cuenta que el código anterior no funciona si lo reemplaza
double
conint64_t
: habrá una conversión implícita adouble
, luego truncamiento aint64_t
, con una posible pérdida de información.data.GetValue ()Comparación : las comparaciones son lo que hay que acertar con los tipos de punto flotante. Sugiero usar un método de comparación como este:
Equidad de redondeo
Suponga que tiene $ 9.99 en la cuenta con un 4% de interés. ¿Cuánto debería ganar el jugador? Con el redondeo de enteros obtienes $ 0.03; con redondeo de punto flotante obtienes $ 0.04. Creo que esto último es más justo.
fuente
int_64t
s.10/3 = 3+3+4
o10/3 = 3+3+3 (+1)
. Para todas las demás operaciones, los enteros funcionan perfectamente, a diferencia del punto flotante, que puede generar problemas de redondeo en cualquier operación.Los tipos de punto flotante en Java (
float
,double
) no son una buena representación para las monedas debido a una razón principal: hay un error de máquina en el redondeo. Incluso si un simple cálculo devuelve un número entero - igual que12.0/2
(6.0), el punto flotante podría erróneamente alrededor de ella (aunque debido a la representación específica de este tipo en la memoria) como6.0000000000001
o5.999999999999998
o similar. Este es el resultado del redondeo específico de la máquina que ocurre en el procesador y es exclusivo de la computadora que lo calculó. Por lo general, rara vez es un problema operar con estos valores, ya que el error es bastante negligente, pero es difícil mostrarlo al usuario.Una posible solución a esto sería utilizar implementaciones personalizadas de tipo de datos de coma flotante, como
BigDecimal
. Admite mejores mecanismos de cálculo que al menos aíslan los errores de redondeo para no ser específicos de la máquina, pero son más lentos en términos de rendimiento.Si necesita una alta productividad, será mejor que se adhiera a los tipos simples. En caso de que opere con datos financieros importantes , y cada centavo sea importante (como una aplicación de Forex o algún juego de casino), le recomendaría que use
Long
olong
.Long
le permitiría manejar grandes cantidades y buena precisión. Supongamos que necesita, digamos, 4 dígitos después del punto decimal, todo lo que necesita es multiplicar la cantidad por 10000. Teniendo experiencia en el desarrollo de juegos de casino en línea, he visto queLong
a menudo se usa para representar el dinero en centavos. . En las aplicaciones de Forex, la precisión es más importante, por lo que necesitará un multiplicador mayor; aún así, los números enteros están libres de problemas de redondeo de la máquina (por supuesto, el redondeo manual como en 3/2 debe manejarse usted mismo).Una opción aceptable sería utilizar los tipos de punto flotante estándar,
Float
yDouble
, si el rendimiento es más importante que la precisión de centésimas de centavo. Luego, en su lógica de visualización, todo lo que necesita es usar un formato predefinido , de modo que la fealdad del redondeo potencial de la máquina no llegue al usuario.fuente
Para juegos de pequeña escala y donde la velocidad del proceso, la memoria es un problema importante (debido a la precisión o el trabajo con el coprocesador matemático puede hacer que sea muy lento), el doble es suficiente.
Pero para juegos a gran escala (por ejemplo, juegos sociales) y donde la velocidad del proceso, la memoria no está limitada, BigDecimal es mejor. Porque aquí
Recursos:
Desde https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency
De Bloch, J., Effective Java, 2nd ed, Item 48:
También eche un vistazo a
fuente
Desea almacenar su moneda
long
y calcular su monedadouble
, al menos como respaldo. Desea que todas las transacciones tengan lugar comolong
.La razón por la que desea almacenar su moneda
long
es que no quiere perder ninguna moneda.Supongamos que usa un
double
y no tiene dinero. Alguien te da tres monedas de diez centavos y luego las recupera.Bueno, eso no es tan genial. Tal vez alguien con $ 10 quiera regalar su fortuna dándole primero tres monedas de diez centavos y luego dándole $ 9.70 a otra persona.
Y luego les devuelves las monedas de diez centavos:
Esto solo está roto.
Ahora, usemos un largo, y realizaremos un seguimiento de las décimas de centavos (entonces 1 = $ 0.001). Demos a todos en el planeta mil millones, ciento doce millones, setenta y cinco mil ciento cuarenta y tres dólares:
Espera, ¿podemos darles a todos más de mil millones de dólares y gastar solo un poco más de dos? El desbordamiento es un desastre aquí.
Así, cada vez que estés calcular una cantidad de dinero a transferir, el uso
double
yMath.round
para obtener unalong
. Luego arregle los saldos (sume y reste ambas cuentas) usandolong
.Su economía no tendrá fugas y aumentará a un billón de dólares.
Hay problemas más difíciles, por ejemplo, ¿qué haces si haces veinte pagos? *, Pero esto debería ayudarte a comenzar.
* Calcula cuál es un pago, redondeado a
long
; luego multiplique por20.0
y verifique que esté dentro del rango; si es así, multiplique el pago por20L
para obtener el monto deducido de su saldo. En general, todas las transacciones deben manejarse comolong
, por lo que realmente necesita resumir todas las transacciones individuales; puede multiplicar como acceso directo, pero debe asegurarse de no agregar errores de redondeo y de que no se desborde, lo que significa que debe verificardouble
antes de realizar el cálculo reallong
.fuente
Me atrevería a decir que cualquier valor que se pueda mostrar al usuario casi siempre debería ser un número entero. El dinero es solo el ejemplo más destacado de esto. Infligir 225 daños cuatro veces a un monstruo con 900 HP y descubrir que todavía le queda 1 HP restará de la experiencia tanto como descubrir que eres una fracción invisible de un centavo por debajo de lo que puedes permitirte.
En el aspecto más técnico, creo que vale la pena señalar que uno no tiene que volver a flotadores para hacer cosas avanzadas como el interés. Mientras tenga suficiente espacio libre en el número entero elegido, escriba una multiplicación y una división sustituirá a una multiplicación por un número decimal, por ejemplo, para agregar un 4%, redondeado hacia abajo:
Para agregar un 4%, redondeado por convenciones estándar:
Aquí no hay imprecisión de coma flotante, el redondeo siempre se divide exactamente en la
.5
marca.Editar, la verdadera pregunta:
Al ver cómo ha ido el debate, empiezo a pensar que esbozar de qué se trata la pregunta puede ser más útil que una simple
int
/float
respuesta. En el centro de la pregunta no se trata de tipos de datos, se trata de tomar el control de los detalles de un programa.El uso de un número entero para representar un valor no entero obliga al programador a ocuparse de los detalles de implementación. "¿Qué precisión usar?" y "¿Qué forma de redondear?" son preguntas que deben responderse explícitamente.
Un flotador, por otro lado, no obliga al programador a preocuparse, ya hace más o menos lo que uno esperaría. Pero dado que un flotador no es de precisión infinita, se producirá un redondeo, y ese redondeo es bastante impredecible.
¿Qué sucede en flotadores de un solo uso y quiere tomar el control del redondeo? Resulta ser casi imposible. La única forma de hacer que un flotante sea realmente predecible es usar solo valores que puedan representarse en
2^n
s completos . Pero esa construcción hace que los flotadores sean bastante difíciles de usar.Por lo tanto, la respuesta a la pregunta simple es: si desea tomar el control, use números enteros, si no, use flotantes.
Pero la pregunta que se debate es solo otra forma de la pregunta: ¿Quieres tomar el control?
fuente
Incluso si es "solo un juego", usaría Money Pattern de Martin Fowler, respaldado por mucho tiempo.
¿Por qué?
Localización (L10n): Usando ese patrón puedes localizar fácilmente la moneda de tu juego. Piensa en los viejos juegos de magnate como "Transport Tycoon". Permiten fácilmente que el jugador cambie la moneda del juego (es decir, de la libra esterlina al dólar estadounidense) para cumplir con la moneda del mundo real.
Y
Eso significa que puede almacenar 9,000 veces la oferta actual de dinero M2 de EE. UU. (~ 10,000 mil millones de dólares). Le da suficiente espacio para usar cualquier otra moneda mundial, probablemente, incluso aquellos que tenían / tienen hiperinflación (si es curioso, vea la inflación alemana posterior a la Primera Guerra Mundial , donde 1 libra de pan era de 3,000,000,000 de marcos)
Long es fácil de persistir, muy rápido y debería darle suficiente espacio para hacer todos los cálculos de interés utilizando solo la aritmética de enteros, la respuesta de eBusiness brinda una explicación sobre cómo hacerlo.
fuente
¿Cuánto trabajo quieres poner en esto? ¿Qué tan importante es la precisión? ¿Le importa rastrear los errores fraccionarios que ocurren con el redondeo y la imprecisión de representar números decimales en un sistema binario?
En última instancia, tiendo a pasar un poco más de tiempo codificando e implementando pruebas unitarias para "casos de esquina" y casos problemáticos conocidos, por lo que estaría pensando en términos de un BigInteger modificado que abarca cantidades arbitrariamente grandes y mantiene los bits fraccionales con un BigDecimal o una parte de BigRational (dos BigIntegers, una para el denominador y otra para el numerador) e incluyen código para mantener la parte fraccional en una fracción real (quizás solo periódicamente) agregando cualquier parte no fraccional al BigInteger principal. Entonces, internamente, haría un seguimiento de todo en centavos solo para mantener las partes fraccionales fuera de los cálculos de la GUI.
Probablemente demasiado complejo para un juego (simple), ¡pero bueno para una biblioteca publicada como código abierto! Solo necesitaría encontrar formas de mantener un buen rendimiento cuando se trata de bits fraccionales ...
fuente
Ciertamente no flota. Con solo 7 dígitos, solo tiene una precisión como:
12,345.67 123,456.7x
Como puede ver, ya con 10 ^ 5 dólares, está perdiendo la cuenta de los centavos. double es utilizable para fines de juego, no en la vida real debido a problemas de precisión.
Pero mucho (y con esto quiero decir 64 bits, o mucho tiempo en C ++) no es suficiente si va a rastrear transacciones y resumirlas. Es suficiente para mantener las tenencias netas de cualquier empresa, pero todas las transacciones durante un año podrían desbordarse. Eso depende de cuán grande sea tu "mundo" financiero en el juego.
fuente
Otra solución que agregaré aquí es hacer una clase de dinero. La clase sería algo así (incluso podría ser simplemente una estructura).
Esto le permitiría representar los centavos en este caso como un entero y los dólares enteros como un entero. Como tiene un indicador separado por ser negativo, puede usar enteros sin signo para sus valores que duplican la cantidad posible (también podría escribir una clase de números que aplique esta idea a los números para obtener REALMENTE números grandes). Todo lo que necesitaría hacer es sobrecargar los operadores matemáticos y podría usar esto como cualquier tipo de datos antiguo. Ocupa más memoria, pero realmente expande los límites de valor.
Esto podría expandirse aún más para incluir cosas como el número de unidades parciales por unidades enteras para que pueda tener monedas que se subdividan en algo diferente a 100 subunidades, centavos en este caso, por dólar. Podrías tener 125 floopies formando un flooper, por ejemplo.
Editar:
Ampliaré la idea de que también podría tener una especie de tabla de consulta, a la que puede acceder la clase de dinero que proporcionaría un tipo de cambio para su moneda frente a todas las demás monedas. Puede incorporar esto a las funciones de sobrecarga de su operador. Por lo tanto, si intenta agregar automáticamente 4 USD a 4 libras británicas, automáticamente le dará 6.6 libras (al momento de la escritura).
fuente
Como se menciona en uno de los comentarios sobre su pregunta original, este problema se ha cubierto en StackExchange: https://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for- aplicación money-in-java
Básicamente, los tipos de coma flotante nunca deben usarse para representar monedas, y como la mayoría de los idiomas, Java tiene un tipo más apropiado. La propia documentación de Oracle sobre primitivas sugiere usar BigDecimal .
fuente
Usar clase es la mejor manera. Puede ocultar la implementación de su dinero, puede cambiar el doble / largo de lo que quiera en cualquier momento.
Ejemplo
En el uso de su código, simplemente getter, creo que es mejor y puede almacenar métodos más útiles.
fuente