Scrivere numeri in lettere in C#

Il framework .Net offre diversi strumenti per scrivere o leggere in vari tipi dati da/verso stringhe. Una conversione molto particolare è quella di scrivere un numero invece che in cifre a parole, ad esempio 1234 in milleduecentotrentaquattro. Un modo per eseguire questa conversione in maniera integrata con il resto del framework è quella di implementare le interfacce IFormatProvider e ICustomFormatter. Partendo dall’idea di base fornita da Joseph Albahari nel suo C# 6 in a nutshell ho realizzato una classe che esegue la conversione per la lingua italiana:

 

public class WordyFormatProviderITA : IFormatProvider, ICustomFormatter
    {

        IFormatProvider _parent;    

        public WordyFormatProviderITA() : this(CultureInfo.CurrentCulture) { }
        public WordyFormatProviderITA(IFormatProvider parent)
        {
            _parent = parent;
        }

        public object GetFormat(Type formatType)
        {
            if (formatType == typeof(ICustomFormatter)) return this;
            return null;
        }

        public string Format(string format, object arg, IFormatProvider prov)
        {
            // If it's not our format string, defer to the parent provider:
            if (arg == null || format==null || !format.StartsWith("W"))
                return string.Format(_parent, "{0:" + format + "}", arg);

            int c = format.IndexOf(".") == -1 ? 2 : int.Parse(format.Replace("W.",""));
            if (arg is double || arg is float || arg is decimal )
            {
                decimal num = Convert.ToDecimal(arg);
                long parteIntera = (long)Decimal.Truncate(num);
                int parteDecimale = (int)(Math.Round(num - parteIntera, c) * (decimal)Math.Pow(10,c));
                return convertNumberToReadableString(parteIntera) + "/" + parteDecimale.ToString();
            }            
            long l = 0;
            if (long.TryParse(arg.ToString(), out l))
            {
                return convertNumberToReadableString(l);
            }
            return "Invalid argument";
        }

        static readonly string[] unita = { "zero", "uno", "due", "tre", "quattro", "cinque", "sei", "sette", "otto", "nove", "dieci", "undici", "dodici", "tredici", "quattordici", "quindici", "sedici", "diciassette", "diciotto", "diciannove" };
        static readonly string[] decine = { "", "dieci", "venti", "trenta", "quaranta", "cinquanta", "sessanta", "settanta", "ottonta", "novanta" };

        public string convertNumberToReadableString(long num)
        {
            StringBuilder result = new StringBuilder();
            long mod = 0;
            long i = 0;
            
            if (num > 0 && num < 20)
            {
                result.Append( unita[num]);
            }
            else
            {
                if (num < 100)
                {
                    mod = num % 10;
                    i = num / 10;
                    switch (mod)
                    {
                        case 0:
                            result.Append(decine[i]);
                            break;
                        case 1:
                            result.Append(decine[i].Substring(0, decine[i].Length - 1));
                            result.Append(unita[mod]);
                            break;
                        case 8:
                            result.Append(decine[i].Substring(0, decine[i].Length - 1));
                            result.Append(unita[mod]);
                            break;
                        default:
                            result.Append(decine[i] + unita[mod]);
                            break;
                    }
                }
                else
                {
                    if (num < 1000)
                    {
                        mod = num % 100;
                        i = (num - mod) / 100;
                        switch (i)
                        {
                            case 1:
                                result.Append("cento");
                                break;
                            default:
                                result.Append(unita[i]);
                                result.Append("cento");
                                break;
                        }
                        result.Append(convertNumberToReadableString(mod));
                    }
                    else
                    {
                        if (num < 10000)
                        {
                            mod = num % 1000;
                            i = (num - mod) / 1000;
                            switch (i)
                            {
                                case 1:
                                    result.Append("mille");
                                    break;
                                default:
                                    result.Append(unita[i]);
                                    result.Append("mila");
                                    break;
                            }
                            result.Append(convertNumberToReadableString(mod));
                        }
                        else
                        {
                            if (num < 1000000)
                            {
                                mod = num % 1000;
                                i = (num - mod) / 1000;
                                switch ((num - mod) / 1000)
                                {
                                    default:
                                        if (i < 20)
                                        {
                                            result.Append(unita[i]);
                                            result.Append("mila");
                                        }
                                        else
                                        {
                                            result.Append(convertNumberToReadableString(i));
                                            result.Append("mila");
                                        }
                                        break;
                                }
                                result.Append(convertNumberToReadableString(mod));
                            }
                            else
                            {
                                if (num < 1000000000)
                                {
                                    mod = num % 1000000;
                                    i = (num - mod) / 1000000;
                                    switch (i)
                                    {
                                        case 1:
                                            result.Append("unmilione");
                                            break;

                                        default:
                                            result.Append(convertNumberToReadableString(i));
                                            result.Append("milioni");

                                            break;
                                    }
                                    result.Append(convertNumberToReadableString(mod));
                                }
                                else
                                {
                                    if (num < 1000000000000)
                                    {
                                        mod = num % 1000000000;
                                        i = (num - mod) / 1000000000;
                                        switch (i)
                                        {
                                            case 1:
                                                result.Append("unmiliardo");
                                                break;

                                            default:
                                                result.Append(convertNumberToReadableString(i));
                                                result.Append("miliardi");

                                                break;
                                        }
                                        result.Append(convertNumberToReadableString(mod));
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return result.ToString() ;
        }
    }

La funzione convertNumberToReadableString è stata presa da qui. Avendo questa classe tra le proprie utility, è possibile scrivere semplicemente: 

WordyFormatProviderITA f = new WordyFormatProviderITA();
var s = string.Format(f, "Il numero {0} in lettere è {0:W.4}", 9233234.8778);
Console.Write(s);

Che fornisce l’output:

Il numero 9233234,8778 in lettere è novemilioniduecentotrentatremiladuecentotrentaquattro/8778

Il formato è attivato dalla stringa W, nel caso di tipi reali può essere seguito dal parametro .n, dove n è il numero delle cifre dopo la virgola che si vuole stampare come /XX. Insomma è il formato di solito utilizzato sugli assegni.