.net - money - tipo de dato decimal
Calcular el sistema. PrecisiĆ³n y escala decimal (5)
Supongamos que tenemos un número System.Decimal.
Para ilustración, tomemos una cuya representación ToString () sea la siguiente:
d.ToString() = "123.4500"
Se puede decir lo siguiente sobre este decimal. Para nuestros propósitos aquí, la escala se define como el número de dígitos a la derecha del punto decimal. La escala efectiva es similar pero ignora los ceros finales que aparecen en la parte fraccionaria. (En otras palabras, estos parámetros se definen como decimales de SQL más algunos parámetros adicionales para explicar el concepto de System.Decimal de ceros finales en la parte fraccionaria).
- Precisión: 7
- Escala: 4
- Precisión efectiva: 5
- Escala efectiva: 2
Dado un System.Decimal arbitrario, ¿cómo puedo calcular estos cuatro parámetros de manera eficiente y sin convertir a un String y examinar el String? La solución probablemente requiere Decimal.GetBits.
Algunos ejemplos más:
Examples Precision Scale EffectivePrecision EffectiveScale
0 1 (?) 0 1 (?) 0
0.0 2 (?) 1 1 (?) 0
12.45 4 2 4 2
12.4500 6 4 4 2
770 3 0 3 0
(?) Alternativamente, interpretar estas precisiones como cero estaría bien.
Actualmente tengo un problema similar, pero no solo necesito la escala, sino que también necesito la mantisse como entero. Sobre la base de las soluciones anteriores, encuentre el más rápido, se podría encontrar a continuación. Estadísticas: "ViaBits" toma 2,000ms para 7,000,000 cheques en mi máquina. "ViaString" toma 4,000ms para la misma tarea.
public class DecimalInfo {
public BigInteger Mantisse { get; private set; }
public SByte Scale { get; private set; }
private DecimalInfo() {
}
public static DecimalInfo Get(decimal d) {
//ViaBits is faster than ViaString.
return ViaBits(d);
}
public static DecimalInfo ViaBits(decimal d) {
//This is the fastest, I can come up with.
//Tested against the solutions from http://.com/questions/763942/calculate-system-decimal-precision-and-scale
if (d == 0) {
return new DecimalInfo() {
Mantisse = 0,
Scale = 0,
};
} else {
byte scale = (byte)((Decimal.GetBits(d)[3] >> 16) & 31);
//Calculating the mantisse from the bits 0-2 is slower.
if (scale > 0) {
if ((scale & 1) == 1) {
d *= 10m;
}
if ((scale & 2) == 2) {
d *= 100m;
}
if ((scale & 4) == 4) {
d *= 10000m;
}
if ((scale & 8) == 8) {
d *= 100000000m;
}
if ((scale & 16) == 16) {
d *= 10000000000000000m;
}
}
SByte realScale = (SByte)scale;
BigInteger scaled = (BigInteger)d;
//Just for bigger steps, seems reasonable.
while (scaled % 10000 == 0) {
scaled /= 10000;
realScale -= 4;
}
while (scaled % 10 == 0) {
scaled /= 10;
realScale--;
}
return new DecimalInfo() {
Mantisse = scaled,
Scale = realScale,
};
}
}
public static DecimalInfo ViaToString(decimal dec) {
if (dec == 0) {
return new DecimalInfo() {
Mantisse = 0,
Scale = 0,
};
} else {
//Is slower than "ViaBits".
string s = dec.ToString(CultureInfo.InvariantCulture);
int scale = 0;
int trailingZeros = 0;
bool inFraction = false;
foreach (char c in s) {
if (inFraction) {
if (c == ''0'') {
trailingZeros++;
} else {
trailingZeros = 0;
}
scale++;
} else {
if (c == ''.'') {
inFraction = true;
} else if (c != ''-'') {
if (c == ''0''){
trailingZeros ++;
} else {
trailingZeros = 0;
}
}
}
}
if (inFraction) {
return new DecimalInfo() {
Mantisse = BigInteger.Parse(s.Replace(".", "").Substring(0, s.Length - trailingZeros - 1)),
Scale = (SByte)(scale - trailingZeros),
};
} else {
return new DecimalInfo() {
Mantisse = BigInteger.Parse(s.Substring(0, s.Length - trailingZeros)),
Scale = (SByte)(scale - trailingZeros),
};
}
}
}
}
Encontré este artículo cuando necesitaba validar la precisión y la escala antes de escribir un valor decimal en una base de datos. En realidad, se me ocurrió una forma diferente de lograrlo utilizando System.Data.SqlTypes.SqlDecimal, que resultó ser más rápido que los otros dos métodos que se describen aquí.
static DecimalInfo SQLInfo(decimal dec)
{
System.Data.SqlTypes.SqlDecimal x;
x = new System.Data.SqlTypes.SqlDecimal(dec);
return new DecimalInfo((int)x.Precision, (int)x.Scale, (int)0);
}
Sí, necesitarías usar Decimal.GetBits
. Desafortunadamente, luego tiene que trabajar con un entero de 96 bits, y no hay ningún tipo de entero simple en .NET que haga frente a los 96 bits. Por otro lado, es posible que puedas usar Decimal
sí mismo ...
Aquí hay un código que produce los mismos números que sus ejemplos. Esperamos que te sea útil :)
using System;
public class Test
{
static public void Main(string[] x)
{
ShowInfo(123.4500m);
ShowInfo(0m);
ShowInfo(0.0m);
ShowInfo(12.45m);
ShowInfo(12.4500m);
ShowInfo(770m);
}
static void ShowInfo(decimal dec)
{
// We want the integer parts as uint
// C# doesn''t permit int[] to uint[] conversion,
// but .NET does. This is somewhat evil...
uint[] bits = (uint[])(object)decimal.GetBits(dec);
decimal mantissa =
(bits[2] * 4294967296m * 4294967296m) +
(bits[1] * 4294967296m) +
bits[0];
uint scale = (bits[3] >> 16) & 31;
// Precision: number of times we can divide
// by 10 before we get to 0
uint precision = 0;
if (dec != 0m)
{
for (decimal tmp = mantissa; tmp >= 1; tmp /= 10)
{
precision++;
}
}
else
{
// Handle zero differently. It''s odd.
precision = scale + 1;
}
uint trailingZeros = 0;
for (decimal tmp = mantissa;
tmp % 10m == 0 && trailingZeros < scale;
tmp /= 10)
{
trailingZeros++;
}
Console.WriteLine("Example: {0}", dec);
Console.WriteLine("Precision: {0}", precision);
Console.WriteLine("Scale: {0}", scale);
Console.WriteLine("EffectivePrecision: {0}",
precision - trailingZeros);
Console.WriteLine("EffectiveScale: {0}", scale - trailingZeros);
Console.WriteLine();
}
}
Usar ToString es aproximadamente 10 veces más rápido que la solución de Jon Skeet. Si bien esto es razonablemente rápido, el desafío aquí (¡si hay algún candidato!) Es superar el rendimiento de ToString.
Los resultados de rendimiento que obtengo del siguiente programa de prueba son: ShowInfo 239 ms FastInfo 25 ms
using System;
using System.Diagnostics;
using System.Globalization;
public class Test
{
static public void Main(string[] x)
{
Stopwatch sw1 = new Stopwatch();
Stopwatch sw2 = new Stopwatch();
sw1.Start();
for (int i = 0; i < 10000; i++)
{
ShowInfo(123.4500m);
ShowInfo(0m);
ShowInfo(0.0m);
ShowInfo(12.45m);
ShowInfo(12.4500m);
ShowInfo(770m);
}
sw1.Stop();
sw2.Start();
for (int i = 0; i < 10000; i++)
{
FastInfo(123.4500m);
FastInfo(0m);
FastInfo(0.0m);
FastInfo(12.45m);
FastInfo(12.4500m);
FastInfo(770m);
}
sw2.Stop();
Console.WriteLine(sw1.ElapsedMilliseconds);
Console.WriteLine(sw2.ElapsedMilliseconds);
Console.ReadLine();
}
// Be aware of how this method handles edge cases.
// A few are counterintuitive, like the 0.0 case.
// Also note that the goal is to report a precision
// and scale that can be used to store the number in
// an SQL DECIMAL type, so this does not correspond to
// how precision and scale are defined for scientific
// notation. The minimal precision SQL decimal can
// be calculated by subtracting TrailingZeros as follows:
// DECIMAL(Precision - TrailingZeros, Scale - TrailingZeros).
//
// dec Precision Scale TrailingZeros
// ------- --------- ----- -------------
// 0 1 0 0
// 0.0 2 1 1
// 0.1 1 1 0
// 0.01 2 2 0 [Diff result than ShowInfo]
// 0.010 3 3 1 [Diff result than ShowInfo]
// 12.45 4 2 0
// 12.4500 6 4 2
// 770 3 0 0
static DecimalInfo FastInfo(decimal dec)
{
string s = dec.ToString(CultureInfo.InvariantCulture);
int precision = 0;
int scale = 0;
int trailingZeros = 0;
bool inFraction = false;
bool nonZeroSeen = false;
foreach (char c in s)
{
if (inFraction)
{
if (c == ''0'')
trailingZeros++;
else
{
nonZeroSeen = true;
trailingZeros = 0;
}
precision++;
scale++;
}
else
{
if (c == ''.'')
{
inFraction = true;
}
else if (c != ''-'')
{
if (c != ''0'' || nonZeroSeen)
{
nonZeroSeen = true;
precision++;
}
}
}
}
// Handles cases where all digits are zeros.
if (!nonZeroSeen)
precision += 1;
return new DecimalInfo(precision, scale, trailingZeros);
}
struct DecimalInfo
{
public int Precision { get; private set; }
public int Scale { get; private set; }
public int TrailingZeros { get; private set; }
public DecimalInfo(int precision, int scale, int trailingZeros)
: this()
{
Precision = precision;
Scale = scale;
TrailingZeros = trailingZeros;
}
}
static DecimalInfo ShowInfo(decimal dec)
{
// We want the integer parts as uint
// C# doesn''t permit int[] to uint[] conversion,
// but .NET does. This is somewhat evil...
uint[] bits = (uint[])(object)decimal.GetBits(dec);
decimal mantissa =
(bits[2] * 4294967296m * 4294967296m) +
(bits[1] * 4294967296m) +
bits[0];
uint scale = (bits[3] >> 16) & 31;
// Precision: number of times we can divide
// by 10 before we get to 0
uint precision = 0;
if (dec != 0m)
{
for (decimal tmp = mantissa; tmp >= 1; tmp /= 10)
{
precision++;
}
}
else
{
// Handle zero differently. It''s odd.
precision = scale + 1;
}
uint trailingZeros = 0;
for (decimal tmp = mantissa;
tmp % 10m == 0 && trailingZeros < scale;
tmp /= 10)
{
trailingZeros++;
}
return new DecimalInfo((int)precision, (int)scale, (int)trailingZeros);
}
}
public static class DecimalExtensions
{
public static int GetPrecision(this decimal value)
{
return GetLeftNumberOfDigits(value) + GetRightNumberOfDigits(value);
}
public static int GetScale(this decimal value)
{
return GetRightNumberOfDigits(value);
}
/// <summary>
/// Number of digits to the right of the decimal point without ending zeros
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static int GetRightNumberOfDigits(this decimal value)
{
var text = value.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd(''0'');
var decpoint = text.IndexOf(System.Globalization.CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator);
if (decpoint < 0)
return 0;
return text.Length - decpoint - 1;
}
/// <summary>
/// Number of digits to the left of the decimal point without starting zeros
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static int GetLeftNumberOfDigits(this decimal value)
{
var text = Math.Abs(value).ToString(System.Globalization.CultureInfo.InvariantCulture).TrimStart(''0'');
var decpoint = text.IndexOf(System.Globalization.CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator);
if (decpoint == -1)
return text.Length;
return decpoint;
}
}
Mi solución es compatible con la definición de precisión y escala de Oracle para NUMBER (p, s) DataType:
https://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#i16209
Saludos.