Almacenar dinero en una columna decimal: ¿qué precisión y escala?

173

Estoy usando una columna decimal para almacenar valores monetarios en una base de datos, y hoy me preguntaba qué precisión y escala usar.

Dado que las columnas supuestamente de un ancho fijo son más eficientes, estaba pensando que lo mismo podría ser cierto para las columnas decimales. ¿Lo es?

¿Y qué precisión y escala debo usar? Estaba pensando en precisión 24/8. ¿Es exagerado, no es suficiente o está bien?


Esto es lo que he decidido hacer:

  • Almacene las tasas de conversión (cuando corresponda) en la propia tabla de transacciones, como flotante
  • Almacenar la moneda en la tabla de la cuenta.
  • El monto de la transacción será un DECIMAL(19,4)
  • Todos los cálculos que usan una tasa de conversión serán manejados por mi aplicación, así que mantengo el control de los problemas de redondeo

No creo que un problema para la tasa de conversión sea un problema, ya que es principalmente para referencia, y de todos modos lo convertiré a un decimal.

Gracias a todos por su valioso aporte.

Ivan
fuente
2
Hágase la pregunta: ¿es realmente necesario almacenar los datos en forma decimal? ¿No puedo almacenar los datos como centavos / centavos -> enteros?
Terence
55
DECIMAL(19, 4) es una opción popular, verifique esto también marque aquí Formatos de moneda mundial para decidir cuántos lugares decimales usar, la esperanza ayuda.
shaijut

Respuestas:

181

Si está buscando una talla única, sugeriría que DECIMAL(19, 4)es una opción popular (un rápido Google lo confirma). Creo que esto se origina en el antiguo tipo de datos VBA / Access / Jet Currency, siendo el primer tipo decimal de punto fijo en el idioma; Decimalsolo vino en estilo 'versión 1.0' (es decir, no completamente implementado) en VB6 / VBA6 / Jet 4.0.

La regla general para el almacenamiento de valores decimales de punto fijo es almacenar al menos un lugar decimal más del que realmente necesita para permitir el redondeo. Una de las razones para mapear el Currencytipo antiguo en el extremo frontal para DECIMAL(19, 4)escribir en el extremo posterior fue que Currencyexhibía el redondeo de los banqueros por naturaleza, mientras que se DECIMAL(p, s)redondeaba por truncamiento.

Un espacio decimal adicional en el almacenamiento DECIMALpermite implementar un algoritmo de redondeo personalizado en lugar de tomar el valor predeterminado del proveedor (y el redondeo de los banqueros es alarmante, por decir lo menos, para un diseñador que espera que todos los valores que terminan en .5 se redondeen desde cero) .

Sí, me DECIMAL(24, 8)parece una exageración. La mayoría de las monedas se cotizan con cuatro o cinco decimales. Sé de situaciones en las que se requiere una escala decimal de 8 (o más) pero aquí es donde se ha prorrateado una cantidad monetaria 'normal' (digamos cuatro decimales), lo que implica que la precisión decimal debe reducirse en consecuencia (también considere un tipo de coma flotante en tales circunstancias). Y nadie tiene tanto dinero hoy en día para requerir una precisión decimal de 24 :)

Sin embargo, en lugar de un enfoque único para todos, algunas investigaciones pueden estar en orden. Pregúntele a su diseñador o experto en dominios sobre las reglas de contabilidad que pueden ser aplicables: PCGA, UE, etc. Recuerdo vagamente algunas transferencias dentro del estado de la UE con reglas explícitas para redondear a cinco decimales, por lo tanto, las uso DECIMAL(p, 6)para el almacenamiento. Los contadores generalmente parecen favorecer cuatro decimales.


PD Evite el MONEYtipo de datos de SQL Server porque tiene serios problemas de precisión al redondear, entre otras consideraciones, como la portabilidad, etc. Consulte el blog de Aaron Bertrand .


Microsoft y los diseñadores de idiomas eligieron el redondeo bancario porque los diseñadores de hardware lo eligieron [¿cita?]. Está consagrado en los estándares del Instituto de Ingenieros Eléctricos y Electrónicos (IEEE), por ejemplo. Y los diseñadores de hardware lo eligieron porque los matemáticos lo prefieren. Ver Wikipedia ; parafraseando: la edición de 1906 de Probabilidad y teoría de los errores llamó a esto "la regla de la computadora" ("computadoras", que significa humanos que realizan cálculos).

un día cuando
fuente
1
Vea esta respuesta y esta página para obtener más información sobre por qué los diseñadores de idiomas eligen el redondeo de Banker.
Nick Chammas
1
onedaywhen: el redondeo de la banca no se debe a Microsoft. @NickChammas: tampoco es una invención de un diseñador de idiomas. Microsoft y los diseñadores de idiomas básicamente lo eligieron porque los diseñadores de hardware lo eligieron; está consagrado en los estándares del Instituto de Ingenieros Eléctricos y Electrónicos (IEEE), por ejemplo. Y los diseñadores de hardware lo eligieron porque los matemáticos lo prefieren. Ver en.wikipedia.org/wiki/Rounding#History ; parafraseando: la edición de 1906 de Probabilidad y teoría de errores llamó a esto "la regla de la computadora" ("computadoras", que significa humanos que realizan cálculos).
phoog
9
Entonces, ¿qué debo usar para Bitcoin?
Toolkit
1
@onedaywhen ¿por qué es DECIMAL(19, 4)más popular que DECIMAL(19, 2)? La mayoría de las monedas mundiales son solo dos decimales.
cokedude
3
@ zypA13510: sí, ¡mi afirmación es así hace diez años! Pero esta es mi tercera respuesta más votada y representa una buena parte del cambio en lo que respecta a mi representante SO, así que tengo dudas :)
día
105

Recientemente implementamos un sistema que necesita manejar valores en múltiples monedas y convertir entre ellos, y descubrimos algunas cosas de la manera difícil.

NUNCA USE NÚMEROS DE PUNTO FLOTANTE PARA DINERO

La aritmética de punto flotante introduce imprecisiones que pueden no notarse hasta que hayan arruinado algo. Todos los valores deben almacenarse como enteros o tipos de decimales fijos, y si elige usar un tipo de decimales fijos, asegúrese de comprender exactamente qué hace ese tipo debajo del capó (es decir, usa internamente un número entero o coma flotante). tipo).

Cuando necesite hacer cálculos o conversiones:

  1. Convertir valores a coma flotante
  2. Calcular nuevo valor
  3. Redondea el número y vuelve a convertirlo en un número entero.

Cuando convierta un número de coma flotante a un número entero en el paso 3, no solo lo eche: use una función matemática para redondearlo primero. Esto generalmente será round, aunque en casos especiales podría ser flooro ceil. Conozca la diferencia y elija con cuidado.

Almacene el tipo de un número junto al valor

Esto puede no ser tan importante para usted si solo maneja una moneda, pero fue importante para nosotros en el manejo de múltiples monedas. Utilizamos el código de 3 caracteres para una moneda, como USD, GBP, JPY, EUR, etc.

Dependiendo de la situación, también puede ser útil almacenar:

  • Si el número es antes o después de impuestos (y cuál fue la tasa de impuestos)
  • Si el número es el resultado de una conversión (y de qué se convirtió)

Conozca los límites de precisión de los números con los que está tratando

Para valores reales, desea ser tan preciso como la unidad más pequeña de la moneda. Esto significa que no tiene valores menores que un centavo, un centavo, un yen, un fen, etc. No almacene valores con mayor precisión que eso sin ninguna razón.

Internamente, puede optar por tratar con valores más pequeños, en cuyo caso ese es un tipo diferente de valor de moneda . Asegúrese de que su código sepa cuál es cuál y no los mezcla. Evite usar valores de coma flotante incluso aquí.


Sumando todas esas reglas juntas, decidimos las siguientes reglas. Al ejecutar el código, las monedas se almacenan utilizando un número entero para la unidad más pequeña.

class Currency {
   String code;       //  eg "USD"
   int value;         //  eg 2500
   boolean converted;
}

class Price {
   Currency grossValue;
   Currency netValue;
   Tax taxRate;
}

En la base de datos, los valores se almacenan como una cadena en el siguiente formato:

USD:2500

Eso almacena el valor de $ 25.00. Pudimos hacer eso solo porque el código que se ocupa de las monedas no necesita estar dentro de la capa de la base de datos, por lo que todos los valores se pueden convertir en memoria primero. Sin duda, otras situaciones se prestarán a otras soluciones.


Y en caso de que no lo deje claro antes, ¡no use flotador!

Marcus Downing
fuente
1
Nunca digas nunca: a veces las cantidades de dinero son proporcionales y deben sumarse nuevamente más tarde. Ejemplo: dividir el dividendo total (relativamente pequeño) dividido por el número de acciones en emisión (relativamente pequeño) para dar neto por acción. A veces el flotador redondea mejor :)
cuando el
23
Yo defiendo mi nunca. La especificación de coma flotante tiene imprecisiones que sumarán más cálculos que realice. Si necesita almacenar valores inferiores a un centavo o centavo, defina el nivel de precisión que necesita y manténgalo. No uses flotador. Seriamente. Es una mala idea.
Marcus Downing
44
Esta respuesta también se alinea con las mejores prácticas de JavaScript descritas por Douglas Crockford en su "serie Crockford on JavaScript", donde recomienda hacer todos los cálculos de moneda en PENNIES para evitar errores de máquina en el redondeo. Entonces, si está trabajando con monedas en JavaScript, tiene mucho sentido almacenar el valor de esta manera.
paperreduction
1
@onedaywhen Esos casos netos por acción, puede sumar los montos prorrateados y compararlos con el total original e idear una estrategia para lidiar con el resto (cuando se usan tipos decimales / enteros).
Shiv
2
Para Mysql, recomiendo almacenar el número entero (2500) como bigintsi tuviera la intención de ordenar por la cantidad. Y no pierda su tiempo con PHP 32 bits cuando se trata de enteros grandes, actualice a 64 bits o Node.JS;)
Ricky Boyce
4

Cuando maneje dinero en MySQL, use DECIMAL (13,2) si conoce la precisión de sus valores de dinero o use DOUBLE si solo desea un valor aproximado rápido y suficientemente bueno. Entonces, si su aplicación necesita manejar valores monetarios de hasta un billón de dólares (o euros o libras), entonces esto debería funcionar:

DECIMAL(13, 2)

O, si necesita cumplir con GAAP , use:

DECIMAL(13, 4)
pollux1er
fuente
3
¿Puede vincular a la parte específica de las pautas GAAP en lugar del contenido de un documento de 2500 páginas? Gracias.
ReactingToAngularVues
@ReactingToAngularVues parece que la página cambió. Lo sentimos
pollux1er
2

4 decimales le darían la precisión para almacenar las subunidades monetarias más pequeñas del mundo. Puede reducirlo aún más si necesita precisión de micropagos (¿nanopagos ?!).

Yo también prefiero los DECIMALtipos de dinero específicos de DBMS, es más seguro mantener ese tipo de lógica en la aplicación IMO. Otro enfoque en la misma línea es simplemente usar un entero [largo], con formato en ¤unit.subunit para la legibilidad humana (¤ = símbolo de moneda) hecho a nivel de aplicación.

bobince
fuente
1

El tipo de datos de dinero en SQL Server tiene cuatro dígitos después del decimal.

De los libros en línea de SQL Server 2000:

Los datos monetarios representan cantidades de dinero positivas o negativas. En Microsoft® SQL Server ™ 2000, los datos monetarios se almacenan utilizando los tipos de datos money y smallmoney. Los datos monetarios se pueden almacenar con una precisión de cuatro decimales. Use el tipo de datos de dinero para almacenar valores en el rango de -922,337,203,685,477.5808 a +922,337,203,685,477.5807 (requiere 8 bytes para almacenar un valor). Use el tipo de datos smallmoney para almacenar valores en el rango de -214,748.3648 a 214,748.3647 (requiere 4 bytes para almacenar un valor). Si se requiere un mayor número de lugares decimales, utilice el tipo de datos decimales.

Austin Salonen
fuente
1

A veces tendrá que ir a menos de un centavo y hay monedas internacionales que usan grandes demonios. Por ejemplo, puede cobrar a sus clientes 0,088 centavos por transacción. En mi base de datos Oracle, las columnas se definen como NUMBER (20,4)

WW.
fuente
1

Si va a realizar cualquier tipo de operaciones aritméticas en la base de datos (multiplicando las tasas de facturación, etc.), es probable que desee mucha más precisión de la que sugieren las personas aquí, por las mismas razones que nunca desea utilizar algo menos que un valor de coma flotante de doble precisión en el código de la aplicación.

Hank Gay
fuente
Eso era lo que estaba pensando, pero en términos de tasas de cambio (es decir, convertir dólares de Zimbawe a USD). Realizaré algunos experimentos en las bases de datos que estoy usando (psql, sqlite) para ver cómo manejan el redondeo en decimales muy pequeños.
Ivan
Además, ¿los flotadores no tienen problemas de precisión en algunos dbms / idiomas?
Ivan
2
Los flotadores tienen problemas de precisión en TODOS los idiomas.
Marcus Downing
La recomendación más común en estos días es usar precisión arbitraria (pensar BigDecimal), pero durante mucho tiempo fue de doble precisión (pensar en doublelugar de float). Además, la precisión arbitraria tiene importantes penalizaciones de rendimiento en algunos casos. La prueba es definitivamente el enfoque correcto.
Hank Gay
0

Si estuviera utilizando IBM Informix Dynamic Server, tendría un tipo de DINERO que es una variante menor en el tipo DECIMAL o NUMERIC. Siempre es un tipo de punto fijo (mientras que DECIMAL puede ser un tipo de punto flotante). Puede especificar una escala de 1 a 32 y una precisión de 0 a 32 (el valor predeterminado es una escala de 16 y una precisión de 2). Entonces, dependiendo de lo que necesite almacenar, puede usar DECIMAL (16,2), que aún es lo suficientemente grande como para mantener el Déficit Federal de EE. UU., Al centavo más cercano, o puede usar un rango más pequeño o más decimales.

Jonathan Leffler
fuente
0

Creo que, en gran parte, sus requisitos o los de su cliente deberían determinar qué precisión y escala utilizar. Por ejemplo, para el sitio web de comercio electrónico en el que estoy trabajando que trata con dinero solo en libras esterlinas, se me pidió que lo mantuviera en decimal (6, 2).

ayaz
fuente
0

Una respuesta tardía aquí, pero he usado

DECIMAL(13,2)

que estoy en lo cierto al pensar que debería permitir hasta 99,999,999,999.99.

Mike Upjohn
fuente