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.