NumToWordsBase.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Based on RSDN RusCurrency class
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. namespace FastReport.Functions
  6. {
  7. internal abstract class NumToWordsBase
  8. {
  9. #region Private Methods
  10. private string Str(decimal value, WordInfo senior, WordInfo junior)
  11. {
  12. bool minus = false;
  13. if (value < 0)
  14. {
  15. value = -value;
  16. minus = true;
  17. }
  18. long n = (long)value;
  19. int remainder = (int)((value - n + 0.005m) * 100);
  20. if (remainder >= 100)
  21. {
  22. n++;
  23. remainder = 0;
  24. }
  25. StringBuilder r = new StringBuilder();
  26. Str(n, senior, r);
  27. if (minus)
  28. r.Insert(0, GetMinus() + " ");
  29. if (junior != null)
  30. {
  31. r.Append(GetDecimalSeparator() + remainder.ToString("00 "));
  32. r.Append(Case(remainder, junior));
  33. }
  34. r[0] = char.ToUpper(r[0]);
  35. return r.ToString();
  36. }
  37. #endregion
  38. #region Protected Methods
  39. protected abstract string GetFixedWords(bool male, long value);
  40. protected abstract string GetTen(bool male, long value);
  41. protected abstract string GetHund(bool male, long value);
  42. protected abstract WordInfo GetThousands();
  43. protected abstract WordInfo GetMillions();
  44. protected abstract WordInfo GetMilliards();
  45. protected abstract WordInfo GetTrillions();
  46. protected abstract CurrencyInfo GetCurrency(string currencyName);
  47. protected abstract string GetZero();
  48. protected abstract string GetMinus();
  49. protected virtual void Str(long value, WordInfo senior, StringBuilder result)
  50. {
  51. if (value == 0)
  52. result.Append(GetZero() + " " + senior.many + " ");
  53. else
  54. {
  55. if (value % 1000 != 0)
  56. result.Append(Str1000(value, senior, 1));
  57. else
  58. result.Append(" " + senior.many + " ");
  59. value /= 1000;
  60. result.Insert(0, Str1000(value, GetThousands(), 2));
  61. value /= 1000;
  62. result.Insert(0, Str1000(value, GetMillions(), 3));
  63. value /= 1000;
  64. result.Insert(0, Str1000(value, GetMilliards(), 4));
  65. value /= 1000;
  66. result.Insert(0, Str1000(value, GetTrillions(), 5));
  67. result.Replace(" ", " ");
  68. }
  69. }
  70. protected virtual string Str1000(long value, WordInfo info, int counter)
  71. {
  72. long val = value % 1000;
  73. if (val == 0)
  74. return "";
  75. StringBuilder r = new StringBuilder();
  76. // add hundred
  77. string hund = GetHund(info.male, val);
  78. if (hund != "")
  79. r.Append(hund);
  80. // decide whether to use the 100-10 separator or not
  81. string sep100_10 = Get100_10Separator();
  82. if (value < 1000 && hund == "")
  83. sep100_10 = "";
  84. val = val % 100;
  85. if (val < GetFixedWordsCount())
  86. {
  87. // val is less than fixed words count (usually 20), get fixed words
  88. string frac20 = GetFixedWords(info.male, val);
  89. if (frac20 != "")
  90. r.Append(sep100_10 + frac20);
  91. }
  92. else
  93. {
  94. // val is greater than fixed words count (usually 20), get tens separately
  95. string ten = GetTen(info.male, val / 10);
  96. string frac10 = GetFixedWords(info.male, val % 10);
  97. // decide whether to use 10-1 separator or not
  98. if (ten != "" && frac10 != "")
  99. r.Append(sep100_10 + ten + Get10_1Separator() + frac10);
  100. else if (ten != "")
  101. r.Append(sep100_10 + ten);
  102. else if (frac10 != "")
  103. r.Append(sep100_10 + frac10);
  104. }
  105. // add currency/group word
  106. r.Append(" ");
  107. r.Append(Case(value, info));
  108. // make the result starting with letter and ending with space
  109. if (r.Length != 0)
  110. r.Append(" ");
  111. return r.ToString().TrimStart(' ');
  112. }
  113. protected virtual int GetFixedWordsCount()
  114. {
  115. return 20;
  116. }
  117. protected virtual string GetDecimalSeparator()
  118. {
  119. return "";
  120. }
  121. protected virtual string Get10_1Separator()
  122. {
  123. return " ";
  124. }
  125. protected virtual string Get100_10Separator()
  126. {
  127. return " ";
  128. }
  129. protected virtual string Case(long value, WordInfo info)
  130. {
  131. if (value % 100 == 1)
  132. return info.one;
  133. return info.many;
  134. }
  135. #endregion
  136. #region Public Methods
  137. public string ConvertCurrency(decimal value, string currencyName)
  138. {
  139. try
  140. {
  141. CurrencyInfo currency = GetCurrency(currencyName);
  142. return Str(value, currency.senior, currency.junior);
  143. }
  144. catch (KeyNotFoundException e)
  145. {
  146. throw new Exception($"Currency \"{currencyName}\" is not defined in the \"{GetType().Name}\" converter.");
  147. }
  148. catch (NotImplementedException e)
  149. {
  150. throw new Exception("Method " + e.TargetSite.ToString() + " wasn't implemented");
  151. }
  152. catch (Exception e)
  153. {
  154. throw new Exception("There is an exception - " + e.ToString());
  155. }
  156. }
  157. public string ConvertNumber(decimal value, bool male, string one, string two, string many)
  158. {
  159. return Str(value, new WordInfo(male, one, two, many), null);
  160. }
  161. public string ConvertNumber(decimal value, bool male,
  162. string seniorOne, string seniorTwo, string seniorMany,
  163. string juniorOne, string juniorTwo, string juniorMany)
  164. {
  165. return Str(value,
  166. new WordInfo(male, seniorOne, seniorTwo, seniorMany),
  167. new WordInfo(male, juniorOne, juniorTwo, juniorMany));
  168. }
  169. #endregion
  170. }
  171. internal class WordInfo
  172. {
  173. public bool male;
  174. public string one;
  175. public string two;
  176. public string many;
  177. public WordInfo(bool male, string one, string two, string many)
  178. {
  179. this.male = male;
  180. this.one = one;
  181. this.two = two;
  182. this.many = many;
  183. }
  184. public WordInfo(string one, string many)
  185. : this(true, one, many, many)
  186. {
  187. }
  188. public WordInfo(string all)
  189. : this(true, all, all, all)
  190. {
  191. }
  192. }
  193. internal class CurrencyInfo
  194. {
  195. public WordInfo senior;
  196. public WordInfo junior;
  197. public CurrencyInfo(WordInfo senior, WordInfo junior)
  198. {
  199. this.senior = senior;
  200. this.junior = junior;
  201. }
  202. }
  203. }