c# vb - Convertire la stringa di valuta in decimale?




net tryparse (5)

Obbiettivo

Ordina una string che visualizza i dati di valuta come questo $1,995.94 numericamente in un set di dati.

Codice

Attualmente sto usando l'esempio di codice seguente per convertire il valore della string in decimal modo da poterlo ordinare correttamente.

if (sortBy == "checkAmount")
{
    StringBuilder sb = new StringBuilder();
    foreach (var c in Convert.ToString(p.GetType().GetProperty(sortBy).GetValue(p, null)))
    {
        if (!char.IsDigit(c) && c != '.') { continue; }
        sb.Append(c);
    }
    return Convert.ToDecimal(sb.ToString());
}
else
{
    return p.GetType().GetProperty(sortBy).GetValue(p, null);
}

Problema

Qual è un modo migliore per farlo? Funziona, e questo è bello, ma non è molto elegante.

Soluzione finale

La risposta fornita da Servy funziona come previsto , e ho usato questa implementazione per un po ', ma un collega e io abbiamo trovato un modo ancora migliore per documentarlo qui. A proposito, ho finito per utilizzare questa soluzione alla fine.

decimal.Parse(input, NumberStyles.AllowCurrencySymbol | NumberStyles.Number);

Answers

decimal amount = decimal.Parse("$123,456.78",
NumberStyles.AllowCurrencySymbol |
NumberStyles.AllowThousands |
NumberStyles.AllowDecimalPoint);

Ecco una soluzione più semplice:

    public static decimal ToDecimal(this string str)
    {
        return decimal.Parse(str, NumberStyles.Currency);
    }

e il test unitario:

    [Test]
    public void ToDecimal_Convert_String_To_Decimal()
    {
        Assert.AreEqual(1234M, "1234".ToDecimal());
        Assert.AreEqual(-1234.56M, "$(1,234.56)".ToDecimal());
        Assert.AreEqual(1234.56M, "$1,234.56".ToDecimal());
    }

public static decimal ToDecimalFromStringDecimalOrMoneyFormattedDecimal(this string s)
{
    try
    {
        return decimal.Parse(s);
    }
    catch
    {
        var numberWithoutMoneyFormatting = Regex.Replace(s, @"[^\d.-]", "");
        return decimal.Parse(numberWithoutMoneyFormatting);
    }
}

[Test]
public void Test_ToDecimalFromStringDecimalOrMoneyFormattedDecimal()
{
    Assert.That("$ 500".ToDecimalFromStringDecimalOrMoneyFormattedDecimal() == (decimal)500);
    Assert.That("R -500".ToDecimalFromStringDecimalOrMoneyFormattedDecimal() == (decimal)-500);
    Assert.That("-$ 500".ToDecimalFromStringDecimalOrMoneyFormattedDecimal() == (decimal)-500);
    Assert.That("P 500.90".ToDecimalFromStringDecimalOrMoneyFormattedDecimal() == (decimal)500.9);
    Assert.That("$ -50 0,090,08.08".ToDecimalFromStringDecimalOrMoneyFormattedDecimal() == (decimal)-50009008.08);
}

Ecco un metodo che ricorda più da vicino il codice che hai fornito

public static decimal Parse(string input)
{
    return decimal.Parse(Regex.Replace(input, @"[^\d.]", ""));
}

Ecco un'opzione che supporterà i numeri negativi e si fermerà se trova un secondo valore di periodo, riducendo così il numero di stringhe che restituisce che non sono valori decimal validi. Ha anche alcune altre modifiche non viste nell'OP per gestire casi aggiuntivi che il tuo codice attuale non ha.

public static decimal Parse(string input)
{
    return decimal.Parse(Regex.Match(input, @"-?\d{1,3}(,\d{3})*(\.\d+)?").Value);
}

TL; DR La soluzione migliore è s.replace('.','',1).isdigit()

Ho fatto alcuni benchmarks confrontando i diversi approcci

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

import re    
def is_number_regex(s):
    """ Returns True is string is a number. """
    if re.match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()

Se la stringa non è un numero, il blocco except è piuttosto lento. Ma ancora più importante, il metodo try-except è l'unico approccio che gestisce correttamente le notazioni scientifiche.

funcs = [
          is_number_tryexcept, 
          is_number_regex,
          is_number_repl_isdigit
          ]

a_float = '.1234'

print('Float notation ".1234" is not supported by:')
for f in funcs:
    if not f(a_float):
        print('\t -', f.__name__)

La notazione floating ".1234" non è supportata da:
- is_number_regex

scientific1 = '1.000000e+50'
scientific2 = '1e50'


print('Scientific notation "1.000000e+50" is not supported by:')
for f in funcs:
    if not f(scientific1):
        print('\t -', f.__name__)




print('Scientific notation "1e50" is not supported by:')
for f in funcs:
    if not f(scientific2):
        print('\t -', f.__name__)

La notazione scientifica "1.000000e + 50" non è supportata da:
- is_number_regex
- is_number_repl_isdigit
La notazione scientifica "1e50" non è supportata da:
- is_number_regex
- is_number_repl_isdigit

EDIT: i risultati del benchmark

import timeit

test_cases = ['1.12345', '1.12.345', 'abc12345', '12345']
times_n = {f.__name__:[] for f in funcs}

for t in test_cases:
    for f in funcs:
        f = f.__name__
        times_n[f].append(min(timeit.Timer('%s(t)' %f, 
                      'from __main__ import %s, t' %f)
                              .repeat(repeat=3, number=1000000)))

dove sono state testate le seguenti funzioni

from re import match as re_match
from re import compile as re_compile

def is_number_tryexcept(s):
    """ Returns True is string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

def is_number_regex(s):
    """ Returns True is string is a number. """
    if re_match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


comp = re_compile("^\d+?\.\d+?$")    

def compiled_regex(s):
    """ Returns True is string is a number. """
    if comp.match(s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True is string is a number. """
    return s.replace('.','',1).isdigit()







c# .net type-conversion