using System; using System.Diagnostics; using System.Linq; using System.Text; using Developpez.Dotnet.Language; using Developpez.Dotnet.Language.Grammar; using Developpez.Dotnet.Properties; [assembly: NumberSpellerType("", typeof(USEnglishNumberSpeller))] [assembly: NumberSpellerType("en-US", typeof(USEnglishNumberSpeller))] [assembly: NumberSpellerType("en-GB", typeof(GBEnglishNumberSpeller))] [assembly: NumberSpellerType("en-IE", typeof(GBEnglishNumberSpeller))] namespace Developpez.Dotnet.Language { // Source for spelling rules in English: // http://en.wikipedia.org/wiki/Numbers_in_english internal abstract class EnglishNumberSpellerBase : INumberSpeller { #region Words and suffixes private static readonly string[] _zeroToNineteen = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", }; private static readonly string[] _tens = new[] { "", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety", }; private const string _hundred = "hundred"; private static readonly string[] _powerOf1000 = new[] { "", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion" }; private const string _regularOrdinalSuffix = "th"; private static readonly string[] _ordinalZeroToNineteen = new[] { "", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", }; private static readonly string[] _ordinalTens = new[] { "", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetieth", }; private const string _decimalSeparator = "point"; private const string _minus = "minus"; private const string _and = "and"; #endregion #region Private implementation private string InnerSpell(ulong value, bool negative, NumeralAdjective numeralAdjective) { if (value == 0) { if (numeralAdjective == NumeralAdjective.Cardinal) return _zeroToNineteen[0]; throw new ArgumentException(ExceptionMessages.ZeroHasNoOrdinal); } StringBuilder builder = new StringBuilder(); if (negative && value != 0) builder.Append(_minus); ulong tmp = value; for (int p = _powerOf1000.Length - 1; p >= 0; p--) { ulong scale = (ulong) Math.Pow(1000, p); if (p > 0 && tmp < scale) continue; int v = (int)(tmp / scale); if (p > 0 && v == 0) continue; if (builder.Length > 0) builder.Append(" "); builder.Append(SpellLessThan1000(v, p == 0 ? numeralAdjective : NumeralAdjective.Cardinal)); string sPowerOf1000 = " " + _powerOf1000[p]; if (p > 0) builder.Append(sPowerOf1000); tmp = tmp % scale; if (tmp == 0 && p > 0) { if (numeralAdjective == NumeralAdjective.Ordinal) builder.Append(_regularOrdinalSuffix); break; } } return builder.ToString(); } private string SpellLessThan1000(int value, NumeralAdjective numeralAdjective) { Debug.Assert(value < 1000, "Expected value less than 1000"); int hundreds = value / 100; int tensAndUnits = value % 100; if (hundreds == 0) return SpellLessThan100(value, numeralAdjective); string sHundreds = _zeroToNineteen[hundreds] + " " + _hundred; if (tensAndUnits == 0) { if (numeralAdjective == NumeralAdjective.Ordinal) return sHundreds + _regularOrdinalSuffix; return sHundreds; } string sTensAndUnits = SpellLessThan100(tensAndUnits, numeralAdjective); return sHundreds + PostHundredSeparator + sTensAndUnits; } private string SpellLessThan100(int value, NumeralAdjective numeralAdjective) { Debug.Assert(value < 100, "Expected value less than 100"); if (value < 20) { if (numeralAdjective == NumeralAdjective.Ordinal) return _ordinalZeroToNineteen[value]; return _zeroToNineteen[value]; } int tens = value / 10; int units = value % 10; if (units == 0) { if (numeralAdjective == NumeralAdjective.Ordinal) return _ordinalTens[tens]; return _tens[tens]; } string sTens = _tens[tens]; string sUnits = SpellLessThan100(units, numeralAdjective); return sTens + "-" + sUnits; } 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(_zeroToNineteen[0] + " ", 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} {2} {3} {4}", Spell(sign * integralPart, currency.CurrencyGender, NumeralAdjective.Cardinal), currencyName, _and, Spell(decimalPart, currency.CentsGender, NumeralAdjective.Cardinal), centsName); } #endregion #region Implementation of INumberSpeller public string Spell(long value, Gender gender, NumeralAdjective numeralAdjective) { if (value == long.MinValue) return InnerSpell((ulong)(-value), true, numeralAdjective); return InnerSpell((ulong)Math.Abs(value), (value < 0), numeralAdjective); } public string Spell(ulong value, Gender gender, NumeralAdjective numeralAdjective) { return InnerSpell(value, false, 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 #region Abstract members public abstract string PostHundredSeparator { get; } #endregion } internal class GBEnglishNumberSpeller : EnglishNumberSpellerBase { #region Overrides of EnglishNumberSpellerBase public override string PostHundredSeparator { get { return " and "; } } #endregion } internal class USEnglishNumberSpeller : EnglishNumberSpellerBase { #region Overrides of EnglishNumberSpellerBase public override string PostHundredSeparator { get { return " "; } } #endregion } }