using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Developpez.Dotnet.Language;
using Developpez.Dotnet.Language.Grammar;
[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;
#endregion
#region Constructor
static FrenchNumberSpeller()
{
_MINUS = "moins";
_DECIMALSEPARATOR = "virgule";
_ZERO = "zéro";
// 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
};
}
#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)
return _ZERO;
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));
// 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(' ');
}
///
/// 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
}
}