Calculer Système.Précision et échelle décimales

supposons que nous ayons un système.Nombre décimal.

pour illustration, prenons celui dont la représentation ToString () est la suivante:

d.ToString() = "123.4500"

ce qui suit peut être dit à propos de cette décimale. Pour nos besoins ici, l'échelle est définie comme le nombre de chiffres à droite du point décimal. Efficace échelle est similaire, mais ignore les zéros qui se produisent dans la partie fractionnaire. (En d'autres termes, ces paramètres sont définis comme Décimales SQL plus quelques paramètres supplémentaires pour tenir compte du système.Concept décimal des zéros de queue dans la partie fractionnaire.)

  • précision: 7
  • échelle: 4
  • décision effective: 5
  • EffectiveScale: 2

donné un système arbitraire.Décimal, Comment puis-je calculer tous les quatre de ces paramètres efficacement et sans passer à une chaîne et l'examen de la Chaîne? La solution nécessite probablement décimale.GetBits.

quelques autres exemples:

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

(?) L'interprétation alternative de ces précisions comme étant nulles serait très bien.

22
demandé sur Jason Kresowaty 2009-04-18 22:52:20

5 réponses

Oui, vous devez utiliser Decimal.GetBits . Malheureusement, vous devez alors travailler avec un entier de 96 bits, et il n'y a pas de type entier simple dans .NET qui fonctionne avec 96 bits. D'un autre côté, il est possible que vous puissiez utiliser Decimal lui-même...

voici un code qui produit les mêmes nombres que vos exemples. J'espère que vous trouverez 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();
    }
}
25
répondu Jon Skeet 2009-04-18 20:14:11

je suis tombé sur cet article quand j'ai eu besoin de valider la précision et l'échelle avant d'écrire une valeur décimale à une base de données. En fait, j'avais trouvé une façon différente de réaliser cela en utilisant le système.Données.SqlTypes.SqlDecimal qui s'est avéré être plus rapide que les deux autres méthodes décrites ici.

 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);
     }
23
répondu Ben Buck 2010-12-14 11:06:59

en utilisant ToString est environ 10 fois plus rapide que la solution de Jon Skeet. Bien que ce soit assez rapide, le défi ici (s'il ya des preneurs!) est de battre la performance de ToString.

les résultats de performance que j'obtiens du programme de test suivant sont: 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);
    }
}
10
répondu Jason Kresowaty 2009-10-31 00:14:59
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;
    }
}

ma solution est compatible avec la précision Oracle et la définition de l'ÉCHELLE POUR LE TYPE DE DONNÉES nombre(p,s):

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

Cordialement.

0
répondu user1785960 2016-06-06 14:28:14

j'ai actuellement un problème similaire, mais j'ai non seulement besoin de l'échelle, mais aussi besoin de la mantisse comme entier. Sur la base des solutions ci-dessus, s'il vous plaît trouver le plus rapide, je pourrais venir avec, ci-dessous. Statistique: "ViaBits" prend 2 000 ms pour 7 000 000 contrôles sur ma machine. "ViaString" prend 4000 ms pour la même tâche.

    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 /q/calculate-system-decimal-precision-and-scale-60943/"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),
                };
            }
        }
    }
}
0
répondu Andreas 2016-08-15 10:56:26