Calcola il sistema.Decima precisione e scala

Supponiamo di avere un numero System.Decimal.

Per esempio, prendiamo uno la cui rappresentazione ToString () è la seguente:

d.ToString() = "123.4500" 

Quanto segue può essere detto su questo decimale. Per i nostri scopi qui, la scala è definita come il numero di cifre a destra del punto decimale. La scala effettiva è simile ma ignora tutti gli zero finali che si verificano nella parte frazionaria. (In altre parole, questi parametri sono definiti come decimali SQL più alcuni parametri aggiuntivi per tenere conto del concetto System.Decimal degli zero finali nella parte frazionaria.)

  • Precisione: 7
  • Scala: 4
  • EffectivePrecision: 5
  • EffectiveScale: 2

Dato un System.Decimal arbitrario, come posso calcolare tutti e quattro questi parametri in modo efficiente e senza convertire una stringa ed esaminare la stringa? La soluzione probabilmente richiede Decimal.GetBits.

Alcuni altri esempi:

 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 

(?) In alternativa interpretare queste precisioni come zero andrebbe bene.

Sì, avresti bisogno di usare Decimal.GetBits . Sfortunatamente, devi lavorare con un numero intero a 96 bit, e non ci sono tipi interi semplici in .NET che gestiscono 96 bit. D’altra parte, è ansible che tu possa usare Decimal stesso …

Ecco un codice che produce gli stessi numeri dei tuoi esempi. Spero che lo trovi utile 🙂

 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(); } } 

Ho trovato questo articolo quando avevo bisogno di convalidare la precisione e la scala prima di scrivere un valore decimale in un database. In realtà, mi sono imbattuto in un modo diverso per ottenere ciò utilizzando System.Data.SqlTypes.SqlDecimal che si è rivelato più veloce rispetto agli altri due metodi discussi qui.

  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); } 

L’utilizzo di ToString è circa 10 volte più veloce della soluzione di Jon Skeet. Anche se questo è abbastanza veloce, la sfida qui (se ci sono alcuni acquirenti!) È quella di battere le prestazioni di ToString.

I risultati delle prestazioni che ottengo dal seguente programma di test sono: 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); } ///  /// Number of digits to the right of the decimal point without ending zeros ///  ///  ///  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; } ///  /// Number of digits to the left of the decimal point without starting zeros ///  ///  ///  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; } } 

La mia soluzione è compatibile con Oracle precision e la definizione di scala per NUMBER (p, s) DataType:

https://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#i16209

Saluti.

Attualmente ho un problema simile, ma non ho solo bisogno della scala, ma ho anche bisogno della mantisse come numero intero. In base alle soluzioni di cui sopra, si prega di trovare il più veloce, che potrei trovare, di seguito. Statistiche: “ViaBits” richiede 2.000ms per 7.000.000 di controlli sulla mia macchina. “ViaString” richiede 4.000ms per lo stesso compito.

  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://stackoverflow.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), }; } } } }