Trouver le nombre de décimales en valeur décimale quelle que soit la culture

91

Je me demande s'il existe un moyen concis et précis d'extraire le nombre de décimales dans une valeur décimale (en tant qu'int) qui sera sûr à utiliser dans différentes informations de culture?

Par exemple:
19.0 doit renvoyer 1,
27.5999 doit renvoyer 4,
19.12 doit renvoyer 2,
etc.

J'ai écrit une requête qui a fait une division de chaîne sur un point pour trouver des décimales:

int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 
                  ? price.ToString().Split('.').ToList().ElementAt(1).Length 
                  : 0;

Mais il me semble que cela ne fonctionnera que dans les régions qui utilisent le "." comme séparateur décimal et est donc très fragile dans différents systèmes.

Jesse Carter
la source
Un nombre décimal selon le titre de la question
Jesse Carter
Que diriez-vous d'une correspondance de modèle avant Split?. Fondamentalement \ d + (\ D) \ d + où \ D renvoie le séparateur (., Etc)
Anshul
7
Ce n'est pas une question fermée, car elle peut apparaître à première vue. La demande 19.0de retour 1est un détail d'implémentation concernant le stockage interne de la valeur 19.0. Le fait est qu'il est parfaitement légitime que le programme stocke ceci sous la forme 190×10⁻¹ou 1900×10⁻²ou 19000×10⁻³. Tous ces éléments sont égaux. Le fait qu'il utilise la première représentation lorsqu'on lui attribue une valeur de 19.0Met que celle-ci soit exposée lors de l'utilisation ToStringsans spécificateur de format est juste une coïncidence et une chose heureuse. Sauf que ce n'est pas heureux quand les gens comptent sur l'exposant dans les cas où ils ne devraient pas.
ErikE
Si vous voulez un type qui peut contenir "le nombre de décimales utilisées" lors de sa création, afin de pouvoir distinguer 19Mde manière fiable 19.0Mde 19.00M, vous devrez créer une nouvelle classe qui regroupe la valeur sous-jacente comme une propriété et le nombre de décimales comme une autre propriété.
ErikE
1
Même si la classe Decimal peut "distinguer" 19m, de 19,0m à 19,00m? Les chiffres significatifs sont comme l'un de ses principaux cas d'utilisation. Qu'est-ce que 19,0 m * 1,0 m? Semble dire 19.00m, peut-être que les développeurs C # font mal les maths: P? Encore une fois, les chiffres significatifs sont une chose réelle. Si vous n'aimez pas les chiffres significatifs, vous ne devriez probablement pas utiliser la classe Decimal.
Nicholi

Réponses:

168

J'ai utilisé la méthode de Joe pour résoudre ce problème :)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];
burn_LEGION
la source
6
Après avoir examiné cela de plus près et l'avoir vu en action, je le marque comme la réponse car c'est à mon avis la méthode la plus concise et la plus élégante de retour des décimales que j'ai vue ici. Je ferais +1 encore si je pouvais: D
Jesse Carter
9
decimalgarde le nombre de chiffres après la virgule, c'est pourquoi vous trouvez ce "problème", vous devez convertir décimal en double et en décimal à nouveau pour corriger: BitConverter.GetBytes (decimal.GetBits ((decimal) (double) argument) [3]) [ 2];
Burning_LEGION
3
Cela n'a pas fonctionné pour moi. La valeur qui revient de SQL est 21,17, elle indique 4 chiffres. Le type de données est défini comme DECIMAL (12,4) alors peut-être que c'est tout (en utilisant Entity Framework).
PeterX
11
@Nicholi - Non, c'est exceptionnellement mauvais car la méthode repose sur le placement des bits sous - jacents de la décimale - quelque chose qui a de nombreuses façons de représenter le même nombre . Vous ne testeriez pas une classe en fonction de l'état de ses champs privés, n'est-ce pas?
m.edmondson
14
Je ne sais pas ce qui est censé être élégant ou gentil à ce sujet. C'est à peu près aussi obscurci que possible. Qui sait si cela fonctionne même dans tous les cas. Impossible de s'en assurer.
usr
24

Étant donné qu'aucune des réponses fournies n'était assez bonne pour le nombre magique "-0.01f" converti en décimal .. ie: GetDecimal((decimal)-0.01f);
je ne peux que supposer qu'un virus colossal a attaqué tout le monde il y a 3 ans :)
Voici ce qui semble fonctionner mise en œuvre de ce problème diabolique et monstrueux, le problème très compliqué de compter les décimales après le point - pas de chaînes, pas de cultures, pas besoin de compter les bits et pas besoin de lire les forums de mathématiques .. juste de simples mathématiques de 3e année.

public static class MathDecimals
{
    public static int GetDecimalPlaces(decimal n)
    {
        n = Math.Abs(n); //make sure it is positive.
        n -= (int)n;     //remove the integer part of the number.
        var decimalPlaces = 0;
        while (n > 0)
        {
            decimalPlaces++;
            n *= 10;
            n -= (int)n;
        }
        return decimalPlaces;
    }
}

private static void Main(string[] args)
{
    Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333
    Console.WriteLine(1/3f); //this is 0.3333333

    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m));                  //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m));                  //28
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f)));     //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m));             //5
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0));                     //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m));                 //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f));   //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f));        //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f));       //2
}
GY
la source
6
Votre solution échouera pour un certain nombre de cas contenant des zéros de fin et les chiffres sont SIGNIFICATIFS. 0,01 m * 2,0 m = 0,020 m. Doit être 3 chiffres, votre méthode renvoie 2. Vous semblez comprendre de manière incorrecte ce qui se passe lorsque vous transtypez 0,01f en décimal. Les virgules flottantes ne sont pas intrinsèquement précises, donc la valeur binaire réelle stockée pour 0.01f n'est pas exacte. Lorsque vous transtypez en décimal (une notation numérique très structurée), vous risquez de ne pas obtenir 0,01 m (vous obtenez en fait 0,010 m). La solution GetBits est en fait correcte pour obtenir le nombre de chiffres à partir d'un décimal. La façon dont vous convertissez en décimal est la clé.
Nicholi
2
@Nicholi 0.020m est égal à 0.02m .. les zéros de fin ne sont pas significatifs. OP demande "quelle que soit la culture" dans le titre et explique encore plus précisément "... qui sera sûr à utiliser dans différentes informations de culture .." - donc je pense que ma réponse reste encore plus valable que d'autres.
GY
6
OP a dit spécifiquement: "19.0 devrait renvoyer 1". Ce code échoue dans ce cas.
daniloquio
9
peut-être que ce n'est pas ce que l'OP voulait, mais cette réponse répond mieux à mes besoins que la première réponse à cette question
Arsen Zahray
2
Les deux premières lignes doivent être remplacées par n = n % 1; if (n < 0) n = -n;car une valeur plus grande que int.MaxValueprovoquera un OverflowException, par exemple 2147483648.12345.
Loathing
23

J'utiliserais probablement la solution dans la réponse de @ fixagon .

Cependant, bien que la structure Decimal n'ait pas de méthode pour obtenir le nombre de décimales, vous pouvez appeler Decimal.GetBits pour extraire la représentation binaire, puis utiliser la valeur entière et l'échelle pour calculer le nombre de décimales.

Ce serait probablement plus rapide que le formatage sous forme de chaîne, bien que vous deviez traiter énormément de décimales pour remarquer la différence.

Je vais laisser la mise en œuvre comme un exercice.

Joe
la source
1
Merci @Joe, c'est une façon vraiment sympa de l'aborder. En fonction de ce que mon patron pense de l'utilisation de l'autre solution, je vais jeter un oeil à la mise en œuvre de votre idée. Ce serait certainement un exercice amusant :)
Jesse Carter
17

L'une des meilleures solutions pour trouver le nombre de chiffres après la virgule décimale est indiquée dans le message de burn_LEGION .

Ici, j'utilise des parties d'un article du forum STSdb: Nombre de chiffres après la virgule décimale .

Dans MSDN, nous pouvons lire l'explication suivante:

"Un nombre décimal est une valeur à virgule flottante qui se compose d'un signe, une valeur numérique où chaque chiffre de la valeur est compris entre 0 et 9, et un facteur d'échelle qui indique la position d'une virgule flottante qui sépare l'intégrale et la fraction parties de la valeur numérique. "

Et aussi:

"La représentation binaire d'une valeur décimale se compose d'un signe de 1 bit, d'un nombre entier de 96 bits et d'un facteur de mise à l'échelle utilisé pour diviser l'entier de 96 bits et spécifier quelle partie est une fraction décimale. Le facteur de mise à l'échelle est implicitement le nombre 10, élevé à un exposant compris entre 0 et 28. "

Au niveau interne, la valeur décimale est représentée par quatre valeurs entières.

Représentation interne décimale

Il existe une fonction GetBits accessible au public pour obtenir la représentation interne. La fonction renvoie un tableau int []:

[__DynamicallyInvokable] 
public static int[] GetBits(decimal d)
{
    return new int[] { d.lo, d.mid, d.hi, d.flags };
}

Le quatrième élément du tableau renvoyé contient un facteur d'échelle et un signe. Et comme le MSDN le dit, le facteur d'échelle est implicitement le nombre 10, élevé à un exposant allant de 0 à 28. C'est exactement ce dont nous avons besoin.

Ainsi, sur la base de toutes les investigations ci-dessus, nous pouvons construire notre méthode:

private const int SIGN_MASK = ~Int32.MinValue;

public static int GetDigits4(decimal value)
{
    return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}

Ici, un SIGN_MASK est utilisé pour ignorer le signe. Après logique et nous avons également décalé le résultat de 16 bits vers la droite pour recevoir le facteur d'échelle réel. Cette valeur, enfin, indique le nombre de chiffres après la virgule décimale.

Notez qu'ici MSDN indique également que le facteur de mise à l'échelle préserve également les zéros de fin dans un nombre décimal. Les zéros de fin n'affectent pas la valeur d'un nombre décimal dans les opérations arithmétiques ou de comparaison. Cependant, des zéros de fin peuvent être révélés par la méthode ToString si une chaîne de format appropriée est appliquée.

Cette solution ressemble à la meilleure, mais attendez, il y en a plus. En accédant aux méthodes privées en C #, nous pouvons utiliser des expressions pour créer un accès direct au champ flags et éviter de construire le tableau int:

public delegate int GetDigitsDelegate(ref Decimal value);

public class DecimalHelper
{
    public static readonly DecimalHelper Instance = new DecimalHelper();

    public readonly GetDigitsDelegate GetDigits;
    public readonly Expression<GetDigitsDelegate> GetDigitsLambda;

    public DecimalHelper()
    {
        GetDigitsLambda = CreateGetDigitsMethod();
        GetDigits = GetDigitsLambda.Compile();
    }

    private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
    {
        var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");

        var digits = Expression.RightShift(
            Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))), 
            Expression.Constant(16, typeof(int)));

        //return (value.flags & ~Int32.MinValue) >> 16

        return Expression.Lambda<GetDigitsDelegate>(digits, value);
    }
}

Ce code compilé est affecté au champ GetDigits. Notez que la fonction reçoit la valeur décimale en tant que ref, donc aucune copie réelle n'est effectuée - seulement une référence à la valeur. L'utilisation de la fonction GetDigits de DecimalHelper est simple:

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);

C'est la méthode la plus rapide possible pour obtenir le nombre de chiffres après la virgule décimale pour les valeurs décimales.

Kristiyan Dimitrov
la source
3
décimal r = (décimal) -0,01f; et la solution échoue. (sur toutes les réponses que j'ai vues dans cette page ...) :)
GY
5
REMARQUE: À propos de l'ensemble (Decimal) 0.01f, vous transtypez une virgule flottante, par nature PAS PRÉCISE, en quelque chose de très structuré comme un Decimal. Jetez un œil à la sortie de Console.WriteLine ((Decimal) 0.01f). La décimale formée dans la distribution a EN réalité 3 chiffres, c'est pourquoi toutes les solutions proposées disent 3 au lieu de 2. Tout fonctionne réellement comme prévu, le "problème" est que vous vous attendez à ce que les valeurs en virgule flottante soient exactes. Ils ne sont pas.
Nicholi
@Nicholi Votre point échoue lorsque vous vous en rendez compte 0.01et ce 0.010sont des nombres exactement égaux . De plus, l'idée qu'un type de données numérique a une sorte de sémantique «nombre de chiffres utilisés» sur laquelle on peut se fier est complètement erronée (à ne pas confondre avec «nombre de chiffres autorisé». Ne confondez pas la présentation (l'affichage de la valeur d'un nombre dans une base particulière, par exemple, le développement décimal de la valeur indiquée par le développement binaire 111) avec la valeur sous-jacente! Pour rappel, les nombres ne sont pas des chiffres, ni ne sont constitués de chiffres .
ErikE
6
Ils sont équivalents en valeur, mais pas en chiffres significatifs. Ce qui est un cas d'utilisation important de la classe Decimal. Si je demandais combien de chiffres il y a dans le littéral 0,010 m, diriez-vous seulement 2? Même si de nombreux professeurs de mathématiques / sciences du monde entier vous diraient que le 0 final est significatif? Le problème auquel nous faisons référence se manifeste par la conversion de virgule flottante en décimal. Pas l'utilisation de GetBits lui-même, qui fait exactement comme il est documenté. Si vous ne vous souciez pas des chiffres significatifs, alors oui, vous avez un problème et vous ne devriez probablement pas utiliser la classe Decimal en premier lieu.
Nicholi
1
@theberserker Autant que je me souvienne, il n'y avait pas de problème - cela devrait fonctionner dans les deux sens.
Kristiyan Dimitrov
13

Se fier à la représentation interne des décimales n'est pas cool.

Que dis-tu de ça:

    int CountDecimalDigits(decimal n)
    {
        return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
                //.TrimEnd('0') uncomment if you don't want to count trailing zeroes
                .SkipWhile(c => c != '.')
                .Skip(1)
                .Count();
    }
Clément
la source
11

vous pouvez utiliser InvariantCulture

string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);

une autre possibilité serait de faire quelque chose comme ça:

private int GetDecimals(decimal d, int i = 0)
{
    decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
    if (Math.Round(multiplied) == multiplied)
        return i;
    return GetDecimals(d, i+1);
}
fixagon
la source
Comment cela m'aide-t-il à trouver le nombre de décimales dans la décimale? Je n'ai aucun problème à convertir la décimale en une chaîne qui convient à toutes les cultures. Selon la question, j'essaie de trouver le nombre de décimales qui étaient sur la décimale
Jesse Carter
@JesseCarter: Cela signifie que vous pouvez toujours vous séparer ..
Austin Salonen
@AustinSalonen Vraiment? Je ne savais pas que l'utilisation d'InvariantCulture imposerait l'utilisation d'un point comme séparateur décimal
Jesse Carter
comme vous l'avez fait auparavant, il convertira toujours le prix en chaîne avec un. comme séparateur décimal. mais ce n'est pas la manière la plus élégante à mon avis ...
fixagon
@JesseCarter: NumberFormatInfo.NumberDecimalSeparator
Austin Salonen
8

La plupart des gens ici ne semblent pas savoir que le nombre décimal considère les zéros de fin comme importants pour le stockage et l'impression.

Ainsi, 0,1 m, 0,10 m et 0,100 m peuvent être comparés comme égaux, ils sont stockés différemment (en tant que valeur / échelle 1/1, 10/2 et 100/3, respectivement), et seront imprimés comme 0,1, 0,10 et 0,100, respectivement , par ToString().

En tant que telles, les solutions qui rapportent "une précision trop élevée" rapportent en fait la précision correcte , selon decimalles conditions.

De plus, les solutions basées sur les mathématiques (comme la multiplication par des puissances de 10) seront probablement très lentes (la décimale est ~ 40x plus lente que le double pour l'arithmétique, et vous ne voulez pas non plus mélanger en virgule flottante car cela risque d'introduire une imprécision. ). De même, la conversion vers intou longcomme moyen de troncature est sujette aux erreurs ( decimala une plage beaucoup plus grande que l'une ou l'autre - elle est basée sur un entier de 96 bits).

Bien que n'étant pas élégant en tant que tel, ce qui suit sera probablement l'un des moyens les plus rapides d'obtenir la précision (lorsqu'il est défini comme "décimales à l'exclusion des zéros de fin"):

public static int PrecisionOf(decimal d) {
  var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0');
  var decpoint = text.IndexOf('.');
  if (decpoint < 0)
    return 0;
  return text.Length - decpoint - 1;
}

La culture invariante garantit un «.» comme point décimal, les zéros de fin sont coupés, puis il suffit de voir combien de positions restent après le point décimal (s'il y en a même une).

Edit: modification du type de retour en int

Zastai
la source
1
@mvmorten Je ne sais pas pourquoi vous avez estimé qu'il était nécessaire de changer le type de retour en int; byte représente plus précisément la valeur renvoyée: non signé et petite plage (0-29, en pratique).
Zastai
1
Je conviens que les solutions itératives et basées sur le calcul sont lentes (en plus de ne pas tenir compte des zéros de fin). Cependant, allouer une chaîne pour cela et opérer dessus n'est pas non plus la chose la plus performante à faire, en particulier dans des contextes critiques de performances et avec un GC lent. L'accès à l'échelle via la logique du pointeur est beaucoup plus rapide et sans allocation.
Martin Tilo Schmitz
Oui, obtenir l' échelle peut être fait beaucoup plus efficacement - mais cela inclurait les zéros de fin. Et les supprimer nécessite de faire de l'arithmétique sur la partie entière.
Zastai le
6

Et voici une autre façon, utilisez le type SqlDecimal qui a une propriété d'échelle avec le nombre de chiffres à droite de la décimale. Convertissez votre valeur décimale en SqlDecimal, puis accédez à Scale.

((SqlDecimal)(decimal)yourValue).Scale
RitchieD
la source
1
En examinant le code de référence Microsoft , le cast en SqlDecimal utilise en interne le GetBytesafin qu'il alloue le tableau Byte au lieu d'accéder aux octets dans un contexte non sécurisé. Il y a même une note et un code commenté dans le code de référence, indiquant cela et comment ils pourraient le faire à la place. Pourquoi ils ne l'ont pas fait est un mystère pour moi. Je resterais à l'écart de cela et accéderais directement aux bits d'échelle au lieu de cacher le GC Alloc dans ce casting, car ce n'est tout simplement pas très évident ce qu'il fait sous le capot.
Martin Tilo Schmitz
4

Jusqu'à présent, presque toutes les solutions répertoriées allouent de la mémoire GC, ce qui est vraiment la façon C # de faire les choses, mais loin d'être idéale dans les environnements critiques en termes de performances. (Ceux qui n'allouent pas de boucles d'utilisation et ne prennent pas non plus en compte les zéros de fin.)

Donc, pour éviter GC Allocs, vous pouvez simplement accéder aux bits d'échelle dans un contexte non sécurisé. Cela peut sembler fragile, mais selon la source de référence de Microsoft , la disposition struct de decimal est séquentielle et contient même un commentaire, pour ne pas changer l'ordre des champs:

    // NOTE: Do not change the order in which these fields are declared. The
    // native methods in this class rely on this particular order.
    private int flags;
    private int hi;
    private int lo;
    private int mid;

Comme vous pouvez le voir, le premier int ici est le champ flags. D'après la documentation et comme mentionné dans d'autres commentaires ici, nous savons que seuls les bits de 16 à 24 codent l'échelle et que nous devons éviter le 31e bit qui code le signe. Puisque int a la taille de 4 octets, nous pouvons le faire en toute sécurité:

internal static class DecimalExtensions
{
  public static byte GetScale(this decimal value)
  {
    unsafe
    {
      byte* v = (byte*)&value;
      return v[2];
    }
  }
}

Cela devrait être la solution la plus performante car il n'y a pas d'allocation GC du tableau d'octets ou de conversions ToString. Je l'ai testé avec .Net 4.x et .Net 3.5 dans Unity 2019.1. S'il y a des versions où cela échoue, veuillez me le faire savoir.

Éditer:

Merci à @Zastai de m'avoir rappelé la possibilité d'utiliser une disposition de structure explicite pour réaliser pratiquement la même logique de pointeur en dehors du code unsafe:

[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
    const byte k_SignBit = 1 << 7;

    [FieldOffset(0)]
    public decimal Value;

    [FieldOffset(0)]
    public readonly uint Flags;
    [FieldOffset(0)]
    public readonly ushort Reserved;
    [FieldOffset(2)]
    byte m_Scale;
    public byte Scale
    {
        get
        {
            return m_Scale;
        }
        set
        {
            if(value > 28)
                throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
            m_Scale = value;
        }
    }
    [FieldOffset(3)]
    byte m_SignByte;
    public int Sign
    {
        get
        {
            return m_SignByte > 0 ? -1 : 1;
        }
    }
    public bool Positive
    {
        get
        {
            return (m_SignByte & k_SignBit) > 0 ;
        }
        set
        {
            m_SignByte = value ? (byte)0 : k_SignBit;
        }
    }
    [FieldOffset(4)]
    public uint Hi;
    [FieldOffset(8)]
    public uint Lo;
    [FieldOffset(12)]
    public uint Mid;

    public DecimalHelper(decimal value) : this()
    {
        Value = value;
    }

    public static implicit operator DecimalHelper(decimal value)
    {
        return new DecimalHelper(value);
    }

    public static implicit operator decimal(DecimalHelper value)
    {
        return value.Value;
    }
}

Pour résoudre le problème d'origine, vous pouvez supprimer tous les champs en plus Valueet Scalepeut-être que cela pourrait être utile pour quelqu'un de tous les avoir.

Martin Tilo Schmitz
la source
1
Vous pouvez également éviter le code dangereux en codant votre propre structure avec une mise en page explicite - mettez une décimale à la position 0, puis des octets / entiers aux emplacements appropriés. Quelque chose comme:[StructLayout(LayoutKind.Explicit)] public struct DecimalHelper { [FieldOffset(0)] public decimal Value; [FieldOffset(0)] public uint Flags; [FieldOffset(0)] public ushort Reserved; [FieldOffset(2)] public byte Scale; [FieldOffset(3)] public DecimalSign Sign; [FieldOffset(4)] public uint ValuePart1; [FieldOffset(8)] public ulong ValuePart2; }
Zastai
Merci @Zastai, bon point. J'ai également intégré cette approche. :)
Martin Tilo Schmitz
1
Une chose à noter: régler l'échelle en dehors de la plage 0-28 provoque une rupture. ToString () a tendance à fonctionner, mais l'arithmétique échoue.
Zastai
Merci encore @Zastai, j'ai ajouté un chèque pour ça :)
Martin Tilo Schmitz
Autre chose: plusieurs personnes ici n'ont pas voulu prendre en compte les zéros décimaux de fin. Si vous définissez a, const decimal Foo = 1.0000000000000000000000000000m;alors diviser une décimale par cela la remettra à l'échelle la plus basse possible (c'est-à-dire n'incluant plus les zéros décimaux de fin). Je n'ai pas évalué cela pour voir si oui ou non c'est plus rapide que l'approche basée sur les chaînes que j'ai suggérée ailleurs.
Zastai
2

J'ai écrit une petite méthode concise hier qui renvoie également le nombre de décimales sans avoir à compter sur des divisions de chaînes ou des cultures, ce qui est idéal:

public int GetDecimalPlaces(decimal decimalNumber) { // 
try {
    // PRESERVE:BEGIN
        int decimalPlaces = 1;
        decimal powers = 10.0m;
        if (decimalNumber > 0.0m) {
            while ((decimalNumber * powers) % 1 != 0.0m) {
                powers *= 10.0m;
                ++decimalPlaces;
            }
        }
return decimalPlaces;
Jesse Carter
la source
@ fix-like-codings similaires à votre deuxième réponse bien que pour quelque chose comme ça, je préfère l'approche itérative plutôt que l'utilisation de la récursivité
Jesse Carter
Le message original indique que: 19.0 should return 1. Cette solution prendra toujours une quantité minimale de 1 décimale et ignorera les zéros de fin. decimal peut avoir ceux-ci car il utilise un facteur d'échelle. Le facteur d'échelle est accessible comme dans les octets 16 à 24 de l'élément avec l'indice 3 dans le tableau obtenu à partir de Decimal.GetBytes()ou en utilisant la logique de pointeur.
Martin Tilo Schmitz
2

J'utilise quelque chose de très similaire à la réponse de Clément:

private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
  string stemp = Convert.ToString(number);

  if (trimTrailingZeros)
    stemp = stemp.TrimEnd('0');

  return stemp.Length - 1 - stemp.IndexOf(
         Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}

N'oubliez pas d'utiliser System.Windows.Forms pour accéder à Application.CurrentCulture

RooiWillie
la source
1

Tu peux essayer:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;
NicoRiff
la source
7
Cela n'échouerait-il pas lorsque la décimale est un nombre entier? [1]
Silvermind
1

J'utilise le mécanisme suivant dans mon code

  public static int GetDecimalLength(string tempValue)
    {
        int decimalLength = 0;
        if (tempValue.Contains('.') || tempValue.Contains(','))
        {
            char[] separator = new char[] { '.', ',' };
            string[] tempstring = tempValue.Split(separator);

            decimalLength = tempstring[1].Length;
        }
        return decimalLength;
    }

entrée décimale = 3,376; var instring = input.ToString ();

appeler GetDecimalLength (instring)

Srikanth
la source
1
Cela ne fonctionne pas pour moi car la représentation ToString () de la valeur decmial ajoute "00" à la fin de mes données - j'utilise un type de données Decimal (12,4) de SQL Server.
PeterX
Pouvez-vous convertir vos données en décimal de type c # et essayer la solution. Pour moi, quand j'utilise Tostring () sur la valeur décimale c #, je ne vois jamais un "00".
Srikanth
1

En utilisant la récursivité, vous pouvez faire:

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}
Mars
la source
Le message original indique que: 19.0 should return 1. Cette solution ignorera les zéros de fin. decimal peut avoir ceux-ci car il utilise un facteur d'échelle. Le facteur d'échelle est accessible comme dans les octets 16 à 24 de l'élément avec l'index 3 dans le Decimal.GetBytes()tableau ou en utilisant la logique du pointeur.
Martin Tilo Schmitz
1
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6
Eva Chang
la source
0

Je suggère d'utiliser cette méthode:

    public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber)
    {
        if (maxNumber == 0)
            return 0;

        if (maxNumber > 28)
            maxNumber = 28;

        bool isEqual = false;
        int placeCount = maxNumber;
        while (placeCount > 0)
        {
            decimal vl = Math.Round(value, placeCount - 1);
            decimal vh = Math.Round(value, placeCount);
            isEqual = (vl == vh);

            if (isEqual == false)
                break;

            placeCount--;
        }
        return Math.Min(placeCount, maxNumber); 
    }
Veysel Ozdemir
la source
0

En tant que méthode d'extension décimale qui prend en compte:

  • Différentes cultures
  • Nombres entiers
  • Nombres négatifs
  • Mettre des zéros à la fin de la décimale (par exemple, 1.2300M renverra 2 et non 4)
public static class DecimalExtensions
{
    public static int GetNumberDecimalPlaces(this decimal source)
    {
        var parts = source.ToString(CultureInfo.InvariantCulture).Split('.');

        if (parts.Length < 2)
            return 0;

        return parts[1].TrimEnd('0').Length;
    }
}
bytedev
la source