using System; using System.Collections.Generic; using System.Linq; using System.Text; using Developpez.Dotnet.Language; using Developpez.Dotnet.Language.Grammar; using Developpez.Dotnet.Properties; [assembly: NumberSpellerType("fr", typeof(FrenchNumberSpeller))] namespace Developpez.Dotnet.Language { // Sources pour les règles d'écriture : // - http://fr.wikipedia.org/wiki/Nombres_en_fran%C3%A7ais // - http://www.miakinen.net/vrac/nombres internal class FrenchNumberSpeller : INumberSpeller { #region Private readonly fields private static readonly string[] _UNITSANDTENS; private static readonly string[] _HUNDREDS; private static readonly string[] _THOUSANDPOWERS; private static readonly string _MINUS; private static readonly string _DECIMALSEPARATOR; private static readonly string _ZERO; private static readonly string _FIRST; private static readonly Dictionary _ORDINALS; #endregion #region Constructor static FrenchNumberSpeller() { _MINUS = "moins"; _DECIMALSEPARATOR = "virgule"; _ZERO = "zéro"; _FIRST = "premier"; // Les nombres jusqu'à cent ont beaucoup "d'exceptions" de nomenclature (23/99). // Il est plus simple de les stocker dans un tableau que de les générer dynamiquement : _UNITSANDTENS = new string[100] { string.Empty, "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf", "vingt", "vingt et un", "vingt-deux", "vingt-trois", "vingt-quatre", "vingt-cinq", "vingt-six", "vingt-sept", "vingt-huit", "vingt-neuf", "trente", "trente et un", "trente-deux", "trente-trois", "trente-quatre", "trente-cinq", "trente-six", "trente-sept", "trente-huit", "trente-neuf", "quarante", "quarante et un", "quarante-deux", "quarante-trois", "quarante-quatre", "quarante-cinq", "quarante-six", "quarante-sept", "quarante-huit", "quarante-neuf", "cinquante", "cinquante et un", "cinquante-deux", "cinquante-trois", "cinquante-quatre", "cinquante-cinq", "cinquante-six", "cinquante-sept", "cinquante-huit", "cinquante-neuf", "soixante", "soixante et un", "soixante-deux", "soixante-trois", "soixante-quatre", "soixante-cinq", "soixante-six", "soixante-sept", "soixante-huit", "soixante-neuf", "soixante-dix", "soixante et onze", "soixante-douze", "soixante-treize", "soixante-quatorze", "soixante-quinze", "soixante-seize", "soixante-dix-sept", "soixante-dix-huit", "soixante-dix-neuf", "quatre-vingt", "quatre-vingt-un", "quatre-vingt-deux", "quatre-vingt-trois", "quatre-vingt-quatre", "quatre-vingt-cinq", "quatre-vingt-six", "quatre-vingt-sept", "quatre-vingt-huit", "quatre-vingt-neuf", "quatre-vingt-dix", "quatre-vingt-onze", "quatre-vingt-douze", "quatre-vingt-treize", "quatre-vingt-quatorze", "quatre-vingt-quinze", "quatre-vingt-seize", "quatre-vingt-dix-sept", "quatre-vingt-dix-huit", "quatre-vingt-dix-neuf" }; _HUNDREDS = new string[10] { string.Empty, "cent", "deux cent", "trois cent", "quatre cent", "cinq cent", "six cent", "sept cent", "huit cent", "neuf cent" }; _THOUSANDPOWERS = new string[7] { "trillion", "mille", "billion", "milliard", "million", "mille", string.Empty }; _ORDINALS = new Dictionary(34); _ORDINALS.Add("un", "unième"); _ORDINALS.Add("deux", "deuxième"); _ORDINALS.Add("trois", "troisième"); _ORDINALS.Add("quatre", "quatrième"); _ORDINALS.Add("cinq", "cinquième"); _ORDINALS.Add("six", "sixième"); _ORDINALS.Add("sept", "septième"); _ORDINALS.Add("huit", "huitième"); _ORDINALS.Add("neuf", "neuvième"); _ORDINALS.Add("dix", "dixième"); _ORDINALS.Add("onze", "onzième"); _ORDINALS.Add("douze", "douzième"); _ORDINALS.Add("treize", "treizième"); _ORDINALS.Add("quatorze", "quatorzième"); _ORDINALS.Add("quinze", "quinzième"); _ORDINALS.Add("seize", "seizième"); _ORDINALS.Add("vingt", "vingtième"); _ORDINALS.Add("vingts", "vingtième"); _ORDINALS.Add("trente", "trentième"); _ORDINALS.Add("quarante", "quarantième"); _ORDINALS.Add("cinquante", "cinquantième"); _ORDINALS.Add("soixante", "soixantième"); _ORDINALS.Add("cent", "centième"); _ORDINALS.Add("cents", "centième"); _ORDINALS.Add("mille", "millième"); _ORDINALS.Add("milles", "millième"); _ORDINALS.Add("million", "millionième"); _ORDINALS.Add("millions", "millionième"); _ORDINALS.Add("milliard", "milliardième"); _ORDINALS.Add("milliards", "milliardième"); _ORDINALS.Add("billion", "billionième"); _ORDINALS.Add("billions", "billionième"); _ORDINALS.Add("trillion", "trillionième"); _ORDINALS.Add("trillions", "trillionième"); } #endregion #region Private methods /// /// Accorder le mot "vingt". /// /// Un "s" si le nombre se termine par vingt et n'est pas suivi de "mille". /// Chaîne vide sinon. private static string MakeTwentyAgree(int value, int thousandPower, NumeralAdjective numeralAdjective) { // Vingt prend un "s" à la fin, si et seulement si : // - il fait partie d'un nombre cardinal. // - il n'est pas situé avant "mille". // - il se termine par quatre-vingts. if (numeralAdjective == NumeralAdjective.Cardinal && thousandPower != 1 && thousandPower != 5 && value == 80) return "s"; else return string.Empty; } /// /// Accorder le mot "cent". /// /// Un "s" si le nombre est un multiple de 100, strictement supérieur à 100 et n'est pas suivi de "mille". /// Chaîne vide sinon. private static string MakeHundredAgree(long hundreds, long tensAndUnits, int thousandPower, NumeralAdjective numeralAdjective) { // Cent prend un "s" à la fin, si et seulement si : // - il fait partie d'un nombre cardinal. // - il n'est pas situé avant "mille". // - il n'est pas suivi de dizaines ni de chiffres (remainder == 0). // - il strictement supérieur à 100 (hundreds > 1). if (numeralAdjective == NumeralAdjective.Cardinal && hundreds > 1 && thousandPower != 1 && thousandPower != 5 && tensAndUnits == 0) return "s"; else return string.Empty; } /// /// Accorder la puissance de mille donnée. /// /// Un "s" si le nombre strictement supérieur à 1 et n'est pas "mille". /// Chaîne vide sinon. private static string MakeThousandPowerAgree(long value, int thousandPower) { // La puissance prend un "s" à la fin, si et seulement si : // - il strictement supérieur à 1. // - il n'est pas "mille". // - ce n'est pas les centaines et unités. if (value > 1 && thousandPower != 1 && thousandPower != 5 && thousandPower != 6) return "s"; else return string.Empty; } /// /// Convertit un nombre entier en toutes lettres. /// /// Nombre entier. /// Le nombre est négatif. /// Genre du nombre entier. /// Nature de l'adjectif numéral. /// Le nombre en toutes lettres. private static string InnerSpell(ulong value, bool negative, Gender gender, NumeralAdjective numeralAdjective) { // Zéro : if (value == 0) { if (numeralAdjective == NumeralAdjective.Cardinal) return _ZERO; else throw new ArgumentException(ExceptionMessages.ZeroHasNoOrdinal); } StringBuilder result = new StringBuilder(); // Valeurs de 1 à 99 // (court-circuite la méthode HighNumbersSpell() pour de meilleures performances) : if (value < 100) result.AppendFormat("{0}{1}", _UNITSANDTENS[value], MakeTwentyAgree((int)value, (_THOUSANDPOWERS.Length - 1), numeralAdjective)); // Valeurs de 100 à 999 // (court-circuite la méthode HighNumbersSpell() pour de meilleures performances) : else if (value < 1000) result.Append(HundredsAndUnitsSpell((int)value, (_THOUSANDPOWERS.Length - 1), numeralAdjective)); else result.Append(HighNumbersSpell(value, numeralAdjective)); if (numeralAdjective == NumeralAdjective.Cardinal) { // Négatif : if (negative) result.Insert(0, _MINUS + " "); // Genre : if (gender == Gender.Feminine && result[result.Length - 2] == 'u' && result[result.Length - 1] == 'n') result.Append("e"); return result.ToString().TrimEnd(' '); } else { // Ordinal : string ordinalResult = result.ToString().TrimEnd(' '); int cutIndex = ordinalResult.LastIndexOfAny(new char[2] { ' ', '-' }); if (cutIndex == -1) { if (ordinalResult == "un") return _FIRST;// Cas particulier... else return _ORDINALS[ordinalResult]; } else return ordinalResult.Substring(0, cutIndex + 1) + _ORDINALS[ordinalResult.Substring(cutIndex + 1, (ordinalResult.Length - (cutIndex + 1)))]; } } /// /// Convertit le nombre entier en toutes lettres (nombres supérieurs à 1000). /// /// Nombre entier (supérieur à 1000). /// Nature de l'adjectif numéral. /// Le nombre en toutes lettres. private static string HighNumbersSpell(ulong value, NumeralAdjective numeralAdjective) { StringBuilder result = new StringBuilder(); ulong remainder = 0; ulong quotient = value; int[] groups = new int[7] { 0, 0, 0, 0, 0, 0, 0 }; int groupIndex = groups.Length - 1; // Découper le nombre en groupes de trois chiffres de la façon suivante : // [0]trillions,[1]milliers de billions,[2]billions,[3]milliards, // [4]millions,[5]milliers,[6]centaines et unités. while (quotient >= 1) { remainder = quotient % 1000; quotient = quotient / 1000; //Math.DivRem(quotient, 1000, out remainder); groups[groupIndex] = (int)remainder; groupIndex--; } // Générer le nombre en toutes lettres : for (groupIndex = 0; groupIndex < groups.Length; groupIndex++) { if (groups[groupIndex] == 0) continue; if (groupIndex == 1) { // Nombre de milliers (> 1 car "un mille" n'existe pas) : if (groups[1] > 1) result.AppendFormat("{0} ", HundredsAndUnitsSpell(groups[groupIndex], groupIndex, numeralAdjective)); // Puissance des milliers (de billions) : result.AppendFormat("{0} ", _THOUSANDPOWERS[groupIndex]); // Ajouter la "billions" dans cette boucle, si le groupe des billions est nul : if (groups[2] == 0) result.AppendFormat("{0}s ", _THOUSANDPOWERS[2]);// Dans ce cas, billions prend un "s" car il y en a plus de mille. } // Exception "un mille" donne "mille" : else if (groupIndex == 5 && groups[5] == 1) result.AppendFormat("{0} ", _THOUSANDPOWERS[groupIndex]); // Cas général : else result.AppendFormat("{0} {1}{2} ", HundredsAndUnitsSpell(groups[groupIndex], groupIndex, numeralAdjective), _THOUSANDPOWERS[groupIndex], MakeThousandPowerAgree(groups[groupIndex], groupIndex)); } return result.ToString().TrimEnd(' '); } /// /// Convertit un nombre entier en toutes lettres (nombres entre 0 et 999). /// /// Nombre entier (entre 0 et 999). /// La puissance de mille du nombre. /// Nature de l'adjectif numéral. /// Le nombre en toutes lettres. private static string HundredsAndUnitsSpell(int value, int thousandPower, NumeralAdjective numeralAdjective) { if (value == 0) return string.Empty; int remainder = 0; int quotient = Math.DivRem(value, 100, out remainder); if (quotient > 0) { if (remainder > 0) return string.Format("{0}{1} {2}{3}", _HUNDREDS[quotient], MakeHundredAgree(quotient, remainder, thousandPower, numeralAdjective), _UNITSANDTENS[remainder], MakeTwentyAgree(remainder, thousandPower, numeralAdjective)); else return string.Format("{0}{1}", _HUNDREDS[quotient], MakeHundredAgree(quotient, remainder, thousandPower, numeralAdjective)); } else return string.Format("{0}{1}", _UNITSANDTENS[remainder], MakeTwentyAgree(remainder, thousandPower, numeralAdjective)); } private string SpellDecimal(long integralPart, int zerosAfterDecimal, long decimalPart, int sign) { if (decimalPart == 0) return Spell(sign * integralPart, Gender.Masculine, NumeralAdjective.Cardinal); string zeros = Enumerable.Repeat(_ZERO + " ", zerosAfterDecimal).Join(string.Empty); return string.Format( "{0}{1} {2} {3}{4}", sign < 0 ? _MINUS + " " : string.Empty, Spell(integralPart, Gender.Masculine, NumeralAdjective.Cardinal), _DECIMALSEPARATOR, zeros, Spell(decimalPart, Gender.Masculine, NumeralAdjective.Cardinal)); } private string SpellAmount(long integralPart, long decimalPart, CurrencyDescriptor currency, int sign) { string currencyName = currency.CurrencyName; string centsName = currency.CentsName; if (integralPart > 1) currencyName = currency.PluralCurrencyName; if (decimalPart > 1) centsName = currency.PluralCentsName; if (decimalPart == 0) return string.Format("{0} {1}", Spell(sign * integralPart, Gender.Masculine, NumeralAdjective.Cardinal), currencyName); if (integralPart == 0) return string.Format("{0} {1}", Spell(sign * decimalPart, Gender.Masculine, NumeralAdjective.Cardinal), centsName); return string.Format( "{0} {1} et {2} {3}", Spell(sign * integralPart, currency.CurrencyGender, NumeralAdjective.Cardinal), currencyName, Spell(decimalPart, currency.CentsGender, NumeralAdjective.Cardinal), centsName); } #endregion #region INumberSpeller implementation public string Spell(long value, Gender gender, NumeralAdjective numeralAdjective) { if (value == long.MinValue) return InnerSpell((ulong)(-value), true, gender, numeralAdjective); return InnerSpell((ulong)Math.Abs(value), (value < 0), gender, numeralAdjective); } public string Spell(ulong value, Gender gender, NumeralAdjective numeralAdjective) { return InnerSpell(value, false, gender, numeralAdjective); } public string Spell(double value, int decimalPlaces) { long i; long d; int z; SpellerHelper.SplitDouble(value, decimalPlaces, true, out i, out d, out z); return SpellDecimal(i, z, d, Math.Sign(value)); } public string Spell(decimal value, int decimalPlaces) { long i; long d; int z; SpellerHelper.SplitDecimal(value, decimalPlaces, true, out i, out d, out z); return SpellDecimal(i, z, d, Math.Sign(value)); } public string SpellAmount(double value, CurrencyDescriptor currency) { long i; long d; int z; SpellerHelper.SplitDouble(value, 2, false, out i, out d, out z); return SpellAmount(i, d, currency, Math.Sign(value)); } public string SpellAmount(decimal value, CurrencyDescriptor currency) { long i; long d; int z; SpellerHelper.SplitDecimal(value, 2, false, out i, out d, out z); return SpellAmount(i, d, currency, Math.Sign(value)); } #endregion } }