Encontre o número de casas decimais em valores decimais, independentemente da cultura

92

Estou me perguntando se existe uma maneira concisa e precisa de extrair o número de casas decimais em um valor decimal (como um int) que será seguro para usar em diferentes informações de cultura.

Por exemplo:
19,0 deve retornar 1,
27,5999 deve retornar 4,
19,12 deve retornar 2,
etc.

Eu escrevi uma consulta que dividia a string em um ponto para encontrar casas decimais:

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

Mas me ocorre que isso só funcionará em regiões que usam o '.' como separador decimal e, portanto, muito frágil em diferentes sistemas.

Jesse Carter
fonte
Uma casa decimal conforme o título da pergunta
Jesse Carter
Que tal alguma correspondência de padrões antes da divisão? Basicamente \ d + (\ D) \ d + onde \ D retorna o separador (., Etc)
Anshul
7
Esta não é uma pergunta fechada, pois pode parecer à primeira vista. Pedir 19.0para retornar 1é um detalhe de implementação referente ao armazenamento interno do valor 19.0. O fato é que é perfeitamente legítimo que o programa armazene isso como 190×10⁻¹ou 1900×10⁻²ou 19000×10⁻³. Todos esses são iguais. O fato de que ele usa a primeira representação quando recebe um valor de 19.0Me isso é exposto ao usar ToStringsem um especificador de formato é apenas uma coincidência, e uma coisa feliz. Exceto que não fica feliz quando as pessoas confiam no expoente em casos onde não deveriam.
ErikE
Se você quiser um tipo que pode levar "número de casas decimais utilizado" quando ele é criado, de modo que você pode distinguir de forma confiável 19Ma partir 19.0Mde 19.00M, você precisa criar uma nova classe que empacota o valor subjacente como uma propriedade eo número de casas decimais como outra propriedade.
ErikE
1
Mesmo que a classe decimal possa "distinguir" 19m, de 19,0m de 19,00m? Os dígitos significativos são como um de seus principais casos de uso. O que é 19,0m * 1,0m? Parece estar dizendo 19,00m, talvez os desenvolvedores de C # estejam fazendo matemática errada: P? Novamente, dígitos significativos são reais. Se você não gosta de dígitos significativos, provavelmente não deve usar a classe Decimal.
Nicholi

Respostas:

168

Usei a maneira de Joe para resolver esse problema :)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];
burn_LEGION
fonte
6
Depois de dar uma olhada nisso e vê-lo em ação, estou marcando-o como a resposta, pois esse é, em minha opinião, o método mais conciso e elegante de retornar casas decimais que vi aqui. Marcaria novamente com +1 se pudesse: D
Jesse Carter
9
decimalmantém o dígito de contagem após a vírgula, é por isso que você encontra este "problema", você deve converter o decimal em duplo e novamente em decimal para corrigir: BitConverter.GetBytes (decimal.GetBits ((decimal) (double) argument) [3]) [ 2];
burn_LEGION
3
Isso não funcionou para mim. O valor que volta do SQL é 21,17 e está dizendo 4 dígitos. O tipo de dados é definido como DECIMAL (12,4), então talvez seja isso (usando o Entity Framework).
PeterX de
11
@Nicholi - Não, isso é excepcionalmente ruim porque o método depende da colocação dos bits subjacentes do decimal - algo que tem muitas maneiras de representar o mesmo número . Você não testaria uma classe com base no estado de seus campos privados, não é?
m.edmondson
14
Não tenho certeza do que deveria ser elegante ou legal nisso. Isso é tão ofuscado quanto possível. Quem sabe se funciona mesmo em todos os casos. Impossível ter certeza.
usr
25

Uma vez que nenhuma das respostas fornecidas foram boas o suficiente para o número mágico "-0.01f" convertido em decimal .. ie: GetDecimal((decimal)-0.01f);
Eu só posso supor que um vírus colossal de peido mental atacou a todos há 3 anos :)
Aqui está o que parece estar funcionando implementação para este problema maligno e monstruoso, o problema muito complicado de contar as casas decimais após o ponto - sem cordas, sem culturas, sem necessidade de contar os bits e sem necessidade de ler fóruns de matemática .. apenas matemática simples de 3ª série.

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
fonte
6
Sua solução falhará para vários casos que contêm zeros à direita e os dígitos são SIGNIFICANTES. 0,01m * 2,0m = 0,020m. Deve ter 3 dígitos, seu método retorna 2. Você parece estar entendendo incorretamente o que acontece quando você converte 0,01f para decimal. Os pontos flutuantes não são inerentemente precisos, então o valor binário real armazenado para 0,01f não é exato. Quando você lança para Decimal (uma notação numérica muito estruturada), você pode não obter 0,01 m (na verdade, você obtém 0,010 m). A solução GetBits é realmente correta para obter o número de dígitos de um decimal. Como você converte para decimal é a chave.
Nicholi de
2
@Nicholi 0,020m é igual a 0,02m .. zeros à direita não são significativos. OP está perguntando "independentemente da cultura" no título e ainda mais específico explica "..que será seguro usar em diferentes informações culturais .." - portanto, acho que minha resposta permanece ainda mais válida do que outras.
GY
6
OP disse especificamente: "19,0 deve retornar 1". Este código falha nesse caso.
daniloquio
9
talvez não seja isso que o OP queria, mas essa resposta atende melhor às minhas necessidades do que a resposta principal desta pergunta
Arsen Zahray
2
As duas primeiras linhas devem ser substituídas por n = n % 1; if (n < 0) n = -n;porque um valor maior que int.MaxValuecausará um OverflowException, por exemplo 2147483648.12345.
Repugnância de
24

Eu provavelmente usaria a solução na resposta de @fixagon .

No entanto, embora a estrutura Decimal não tenha um método para obter o número de decimais, você pode chamar Decimal.GetBits para extrair a representação binária e, em seguida, usar o valor inteiro e a escala para calcular o número de decimais.

Isso provavelmente seria mais rápido do que formatar como uma string, embora você tenha que processar uma quantidade enorme de decimais para notar a diferença.

Vou deixar a implementação como um exercício.

Joe
fonte
1
Obrigado @Joe, essa é uma maneira muito legal de abordar isso. Dependendo de como meu chefe se sente ao usar a outra solução, analisarei a implementação de sua ideia. Com certeza seria um exercício divertido :)
Jesse Carter
17

Uma das melhores soluções para encontrar o número de dígitos após a vírgula decimal é mostrada na postagem de burn_LEGION .

Aqui estou usando partes de um artigo do fórum STSdb: Número de dígitos após a vírgula decimal .

No MSDN podemos ler a seguinte explicação:

"Um número decimal é um valor de ponto flutuante que consiste em um sinal, um valor numérico em que cada dígito no valor varia de 0 a 9 e um fator de escala que indica a posição de um ponto decimal flutuante que separa o integral do fracionário partes do valor numérico. "

E também:

"A representação binária de um valor decimal consiste em um sinal de 1 bit, um número inteiro de 96 bits e um fator de escala usado para dividir o inteiro de 96 bits e especificar qual parte dele é uma fração decimal. O fator de escala é implicitamente, o número 10, elevado a um expoente que varia de 0 a 28. "

No nível interno, o valor decimal é representado por quatro valores inteiros.

Representação interna decimal

Há uma função GetBits disponível publicamente para obter a representação interna. A função retorna uma matriz int []:

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

O quarto elemento da matriz retornada contém um fator de escala e um sinal. E, como diz o MSDN, o fator de escala é implicitamente o número 10, elevado a um expoente que varia de 0 a 28. Isso é exatamente o que precisamos.

Assim, com base em todas as investigações acima, podemos construir nosso método:

private const int SIGN_MASK = ~Int32.MinValue;

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

Aqui, um SIGN_MASK é usado para ignorar o sinal. Depois de lógico e, também mudamos o resultado com 16 bits à direita para receber o fator de escala real. Este valor, por fim, indica o número de dígitos após a vírgula decimal.

Observe que aqui o MSDN também informa que o fator de escala também preserva quaisquer zeros à direita em um número decimal. Os zeros à direita não afetam o valor de um número decimal em operações aritméticas ou de comparação. No entanto, zeros finais podem ser revelados pelo método ToString se uma seqüência de caracteres de formato apropriada for aplicada.

Esta solução parece ser a melhor, mas espere, tem mais. Ao acessar métodos privados em C # podemos usar as expressões de construir um acesso directo ao campo de bandeiras e evitar construir a matriz 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);
    }
}

Este código compilado é atribuído ao campo GetDigits. Observe que a função recebe o valor decimal como ref, portanto, nenhuma cópia real é executada - apenas uma referência ao valor. Usar a função GetDigits do DecimalHelper é fácil:

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

Este é o método mais rápido possível para obter o número de dígitos após a vírgula decimal para valores decimais.

Kristiyan Dimitrov
fonte
3
decimal r = (decimal) -0,01f; e a solução falha. (em todas as respostas que vi nesta página ...) :)
GY
5
NOTA: Sobre a coisa inteira (decimal) 0.01f, você está convertendo um ponto flutuante, inerentemente NÃO PRECISO, para algo muito estruturado como um decimal. Dê uma olhada na saída de Console.WriteLine ((Decimal) 0.01f). O Decimal sendo formado na conversão REALMENTE tem 3 dígitos, é por isso que todas as soluções fornecidas dizem 3 em vez de 2. Tudo está realmente funcionando como esperado, o "problema" é que você espera que os valores de ponto flutuante sejam exatos. Eles não são.
Nicholi de
@Nicholi Seu ponto falha quando você percebe isso 0.01e 0.010são números exatamente iguais . Além disso, a ideia de que um tipo de dado numérico tem algum tipo de semântica de "número de dígitos usados" que pode ser confiável é completamente errada (não deve ser confundida com "número de dígitos permitidos". Não confunda a apresentação (a exibição de o valor de um número em uma base particular, por exemplo, a expansão decimal do valor indicado pela expansão binária 111) com o valor subjacente! Para reiterar, os números não são dígitos, nem são compostos de dígitos .
ErikE
6
Eles são equivalentes em valor, mas não em dígitos significativos. Que é um grande caso de uso da classe Decimal. Se eu perguntasse quantos dígitos existem no literal 0,010 m, você diria apenas 2? Mesmo que muitos professores de matemática / ciências em todo o mundo digam que o 0 final é significativo? O problema ao qual estamos nos referindo é manifestado pela conversão de pontos flutuantes em decimais. Não o uso do próprio GetBits, que está fazendo exatamente como está documentado. Se você não se importa com dígitos significativos, então sim, você tem um problema e provavelmente não deveria estar usando a classe Decimal em primeiro lugar.
Nicholi
1
@theberserker Pelo que me lembro, não havia problema - deveria funcionar nos dois sentidos.
Kristiyan Dimitrov
13

Depender da representação interna de decimais não é legal.

Que tal agora:

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

você pode usar o InvariantCulture

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

outra possibilidade seria fazer algo assim:

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);
}
fixágono
fonte
Como isso me ajuda a encontrar o número de casas decimais na casa decimal? Não tenho nenhum problema em converter o decimal em uma string que é boa em qualquer cultura. De acordo com a pergunta, estou tentando encontrar o número de casas decimais que estavam na casa decimal
Jesse Carter
@JesseCarter: Isso significa que você sempre pode dividir ..
Austin Salonen de
@AustinSalonen é mesmo? Eu não sabia que usar InvariantCulture obrigaria o uso de um ponto como separador decimal
Jesse Carter
como você fez antes, sempre terá o preço de amarrar com a. como separador decimal. mas não é a forma mais elegante na minha opinião ...
fixagon
@JesseCarter: NumberFormatInfo.NumberDecimalSeparator
Austin Salonen
8

A maioria das pessoas aqui parece não estar ciente de que o decimal considera os zeros à direita como significativos para armazenamento e impressão.

Portanto, 0,1m, 0,10m e 0,100m podem ser comparados como iguais, eles são armazenados de forma diferente (como valor / escala 1/1, 10/2 e 100/3, respectivamente), e serão impressos como 0,1, 0,10 e 0,100, respectivamente , por ToString().

Como tal, as soluções que relatam "uma precisão muito alta" estão, na verdade, relatando a precisão correta , emdecimal termos de.

Além disso, as soluções baseadas em matemática (como a multiplicação por potências de 10) provavelmente serão muito lentas (decimal é ~ 40x mais lento do que o dobro para aritmética, e você não quer misturar em ponto flutuante porque isso provavelmente introduzirá imprecisão ) Da mesma forma, a conversão para intou longcomo meio de truncamento está sujeita a erros (decimal tem um intervalo muito maior do que qualquer um deles - é baseado em um número inteiro de 96 bits).

Embora não seja elegante como tal, o seguinte provavelmente será uma das maneiras mais rápidas de obter a precisão (quando definido como "casas decimais excluindo zeros à direita"):

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

A cultura invariável garante um '.' como ponto decimal, os zeros à direita são aparados, e então é apenas uma questão de ver quantas posições permanecem após o ponto decimal (se houver um).

Editar: tipo de retorno alterado para int

Zastai
fonte
1
@mvmorten Não tenho certeza por que você sentiu que era necessário alterar o tipo de retorno para int; byte representa com mais precisão o valor retornado: sem sinal e intervalo pequeno (0-29, na prática).
Zastai
1
Concordo que as soluções iterativas e baseadas em cálculos são lentas (além de não levarem em conta os zeros à direita). No entanto, alocar uma string para isso e operar aquilo também não é a coisa mais eficiente a se fazer, especialmente em contextos críticos de desempenho e com um GC lento. Acessar a escala via lógica de ponteiro é muito mais rápido e sem alocação.
Martin Tilo Schmitz
Sim, obter a escala pode ser feito com muito mais eficiência - mas isso incluiria os zeros à direita. E removê-los requer fazer aritmética na parte inteira.
Zastai
6

E aqui está outra maneira, use o tipo SqlDecimal que possui uma propriedade de escala com a contagem dos dígitos à direita do decimal. Converta seu valor decimal para SqlDecimal e acesse Escala.

((SqlDecimal)(decimal)yourValue).Scale
RitchieD
fonte
1
Olhando para o código de referência da Microsoft , a conversão para SqlDecimal usa internamente o, GetBytesportanto, aloca a matriz de bytes em vez de acessar os bytes em um contexto não seguro. Existe até uma nota e um código comentado no código de referência, informando isso e como eles poderiam fazer isso. Por que eles não fizeram isso é um mistério para mim. Eu ficaria longe disso e acessaria os bits de escala diretamente, em vez de esconder o GC Alloc neste elenco, já que não é muito óbvio o que ele faz por baixo do capô.
Martin Tilo Schmitz
4

Até agora, quase todas as soluções listadas estão alocando memória GC, que é a maneira C # de fazer as coisas, mas está longe de ser ideal em ambientes de desempenho crítico. (Aqueles que não alocam usam loops e também não levam os zeros à direita em consideração.)

Portanto, para evitar alocações de GC, você pode apenas acessar os bits de escala em um contexto não seguro. Isso pode parecer frágil, mas de acordo com a fonte de referência da Microsoft , o layout da estrutura do decimal é Sequencial e ainda tem um comentário lá, para não alterar a ordem dos campos:

    // 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;

Como você pode ver, o primeiro int aqui é o campo de sinalizadores. Pela documentação e conforme mencionado em outros comentários aqui, sabemos que apenas os bits de 16-24 codificam a escala e que precisamos evitar o 31º bit que codifica o sinal. Como int tem o tamanho de 4 bytes, podemos fazer isso com segurança:

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

Esta deve ser a solução de melhor desempenho, uma vez que não há alocação de GC da matriz de bytes ou conversões de ToString. Eu testei com .Net 4.xe .Net 3.5 no Unity 2019.1. Se houver alguma versão em que isso falhe, por favor me avise.

Editar:

Agradeço a @Zastai por me lembrar da possibilidade de usar um layout de estrutura explícito para praticamente atingir a mesma lógica de ponteiro fora do código não seguro:

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

Para resolver o problema original, você poderia remover todos os campos além de Valuee, Scalemas talvez pudesse ser útil para alguém ter todos eles.

Martin Tilo Schmitz
fonte
1
Você também pode evitar códigos inseguros codificando sua própria estrutura com layout explícito - coloque um decimal na posição 0 e, em seguida, bytes / ints nos locais apropriados. Algo como:[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
Obrigado @Zastai, bom ponto. Eu também incorporei essa abordagem. :)
Martin Tilo Schmitz
1
Uma coisa a ser observada: definir a escala fora da faixa de 0-28 causa uma quebra. ToString () tende a funcionar, mas a aritmética falha.
Zastai
Obrigado novamente @Zastai, adicionei um cheque para isso :)
Martin Tilo Schmitz
Outra coisa: várias pessoas aqui não queriam levar os zeros decimais à direita em consideração. Se você definir a const decimal Foo = 1.0000000000000000000000000000m;, dividir um decimal por aquele irá redimensioná-lo para a escala mais baixa possível (ou seja, não incluindo mais os zeros decimais à direita). Eu não fiz o benchmarking para ver se é ou não mais rápido do que a abordagem baseada em strings que sugeri em outro lugar.
Zastai
3

Estou usando algo muito semelhante à resposta de Clemente:

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

Lembre-se de usar System.Windows.Forms para obter acesso a Application.CurrentCulture

RooiWillie
fonte
2

Eu escrevi um pequeno método conciso ontem que também retorna o número de casas decimais sem ter que contar com nenhuma divisão de string ou culturas, o que é ideal:

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
fonte
@ fix-like-codings semelhantes à sua segunda resposta, embora para algo assim eu prefira a abordagem iterativa em vez de usar recursão
Jesse Carter
O post original afirma que: 19.0 should return 1. Esta solução sempre assumirá uma quantidade mínima de 1 casa decimal e ignorará os zeros à direita. decimal pode ter esses, pois usa um fator de escala. O fator de escala pode ser acessado como nos bytes 16-24 do elemento com índice 3 no array obtido de Decimal.GetBytes()ou usando a lógica de ponteiro.
Martin Tilo Schmitz
1

Eu uso o seguinte mecanismo em meu código

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

entrada decimal = 3,376; var instring = input.ToString ();

chame GetDecimalLength (instruindo)

Srikanth
fonte
1
Isso não funciona para mim, pois a representação ToString () do valor decimal adiciona "00" ao final dos meus dados - estou usando um tipo de dados Decimal (12,4) do SQL Server.
PeterX de
Você pode converter seus dados para decimal do tipo c # e tentar a solução. Para mim, quando eu uso Tostring () no valor decimal c #, nunca vejo um "00".
Srikanth
1

Usando a recursão, você pode fazer:

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}
Marte
fonte
O post original afirma que: 19.0 should return 1. Esta solução irá ignorar os zeros finais. decimal pode ter esses, pois usa um fator de escala. O fator de escala pode ser acessado como nos bytes 16-24 do elemento com índice 3 no Decimal.GetBytes()array ou usando a lógica de ponteiro.
Martin Tilo Schmitz
1
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6
Eva Chang
fonte
0

Podes tentar:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;
NicoRiff
fonte
7
Isso não falharia quando o decimal é um número inteiro? [1]
Silvermind de
0

Eu sugiro usar este método:

    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
fonte
0

Como um método de extensão decimal que leva em consideração:

  • Culturas diferentes
  • Números inteiros
  • Números negativos
  • Trailing set zeros na casa decimal (por exemplo, 1.2300M retornará 2, não 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
fonte