fork(1) download
  1. /* package whatever; // don't place package name! */
  2.  
  3. import java.math.BigDecimal;
  4. import java.math.RoundingMode;
  5. import java.util.EmptyStackException;
  6. import java.util.Stack;
  7.  
  8. /**
  9.  * Класс калькулятора. Калькулятор со скобками.
  10.  * <p>
  11.  * !!!ВНИМАНИЕ!! Операторы не должны содеражать части названий друг друга.
  12.  * Например, не допускается делать операторы вида "tan" и "ctan". ctan содержит tan. Так нельзя.
  13.  * <p>
  14.  * Чтобы добавить новый оператор, надо внести изменения в 4 места:
  15.  * - REGEX_OPERATORS
  16.  * - getPriority()
  17.  * - isLeftAssociative()
  18.  * - calculateReversePolishNotation()
  19.  */
  20. class Calculator
  21. {
  22. /**
  23.   * Регулярка для проверки числа типа double.
  24.   */
  25. public static final String REGEX_DOUBLE = "^-?(0|[1-9][0-9]*)(\\.[0-9]+)*(E-?[1-9][0-9]*)?$";
  26.  
  27. /**
  28.   * Регулярка для определения операторов.
  29.   */
  30. protected static String REGEX_OPERATORS = "^(\\+|-|\\*|/|\\^|±|sin|cos|tan|ctg|ln)$";
  31. //------------------------------------------------------------------------------------------------------------------
  32.  
  33. /**
  34.   * Исключение, которое бросает калькулятор при ошибках.
  35.   */
  36. public static class CalculatorErrorException extends Exception
  37. {
  38. public CalculatorErrorException (String _msg)
  39. {
  40. super (_msg);
  41. }
  42. }
  43.  
  44. /**
  45.   * Это исключение выдается, когда результат вычисления равен "NaN".
  46.   * Например, при делении на ноль или при возведении отрицательного числа в дробную степень.
  47.   */
  48. public static class CalculatorNaNException extends Exception
  49. {
  50. public CalculatorNaNException (String _msg)
  51. {
  52. super (_msg);
  53. }
  54. }
  55.  
  56. //------------------------------------------------------------------------------------------------------------------
  57.  
  58. /**
  59.   * Вычислить значение переданного выражения.
  60.   *
  61.   * @param _expression Выражение в нормальной форме.
  62.   *
  63.   * @return
  64.   *
  65.   * @throws Exception Бросает исключения в случае синтаксических ошибок или неправильности скобочной структуры.
  66.   */
  67. public static double calculateNormal (String _expression) throws CalculatorErrorException, CalculatorNaNException
  68. {
  69. return calculateReversePolishNotation (convertInfixToRPN (_expression));
  70. }
  71.  
  72. /**
  73.   * Вычислить значение уравнения.
  74.   *
  75.   * @param _expressionNormal Строка с выражением. Может содержать символ "x".
  76.   * @param _x Значение параметра "x". Символ "x" из строки будет заменен на это значение.
  77.   *
  78.   * @return
  79.   *
  80.   * @throws CalculatorErrorException
  81.   */
  82. public static double calculateNormalEquation (String _expressionNormal, double _x) throws CalculatorErrorException, CalculatorNaNException
  83. {
  84. _expressionNormal = _expressionNormal.replaceAll ("[xX]", "(" + _x + ")");
  85.  
  86. return calculateReversePolishNotation (convertInfixToRPN (_expressionNormal));
  87. }
  88.  
  89. //------------------------------------------------------------------------------------------------------------------
  90. // МЕТОДЫ КОНВЕРТИРОВАНИЯ INFIX TO RPN, А ТАКЖЕ ВЫЧИСЛЕНИЕ RPN.
  91. //------------------------------------------------------------------------------------------------------------------
  92.  
  93. /**
  94.   * Преобразовать инфиксную скобочную запись арифметического выражения в обратную польскую нотацию.
  95.   *
  96.   * @param _exp Выражение в обычной инфиксной скобочной форме..
  97.   *
  98.   * @return Выражение в форме RPN.
  99.   */
  100. public static String convertInfixToRPN (String _exp) throws CalculatorErrorException, CalculatorNaNException
  101. {
  102. // Стек. На нем будем вычислять.
  103. Stack<String> stack = new Stack<String> ();
  104.  
  105. // Итоговая строка
  106. String result = "";
  107.  
  108. String exp = formatExpToSystemView (_exp);
  109. // System.out.println ("ToRPN: Выражение в инфиксной форме (User): \"" + _exp + "\".");
  110. // System.out.println ("ToRPN: Выражение в инфиксной форме (System): \"" + exp + "\".");
  111. if (!checkBracketsStructure (exp))
  112. {
  113. throw new CalculatorErrorException ("ToRPN: Структура скобок неверна!");
  114. }
  115.  
  116. // Проходимся по символам.
  117. String[] chars = exp.trim ().split ("\\s");
  118. for (String token : chars)
  119. {
  120. //----------------------------
  121. if (isNumber (token))
  122. {
  123. result += token + " ";
  124. }
  125. else if (token.equals ("("))
  126. {
  127. stack.push (token);
  128. }
  129. else if (token.equals (")"))
  130. {
  131. try
  132. {
  133. while (!stack.peek ().equals ("("))
  134. {
  135. result += stack.pop () + " ";
  136. }
  137. stack.pop ();// Извлекаем открывающую скобку, не добавляя ее в выходную строку.
  138. }
  139. {
  140. throw new CalculatorErrorException ("ToRPN: Синтаксическая ошибка при конвертации в RPN: неверный разделитель. Выражение: \"" + exp + "\".");
  141. }
  142. }
  143. else
  144. {
  145. while (!stack.isEmpty ())
  146. {
  147. // Если на вершине стека оператор и приоритет текущего оператора меньше чем топового
  148. // - кладем топовый в вывод.
  149. if (isOperator (stack.peek ()) &&
  150. ((isLeftAssociative (token) && (getPriority (token) <= getPriority (stack.peek ())))
  151. || (!isLeftAssociative (token) && (getPriority (token) < getPriority (stack.peek ())))))
  152. {
  153. // Кладем топовый оператор в вывод.
  154. result += stack.pop () + " ";
  155. }
  156. else
  157. {
  158. break;
  159. }
  160. }
  161.  
  162. // Помещаем текущий оператор в стек.
  163. stack.push (token);
  164. }
  165. }
  166.  
  167. // Выталкиваем оставшиеся элементы из стека.
  168. String stackLost = "";
  169. while (!stack.isEmpty ())
  170. {
  171. stackLost += stack.pop () + " ";
  172. }
  173. result += stackLost;
  174.  
  175. // System.out.println ("ToRPN: Проверка. Стек: \"" + stackLost + "\".");
  176. // System.out.println ("ToRPN: Результат в ОПН: \"" + result + "\".");
  177. // System.out.println ("ToRPN: Результат вычисления ОПН: \"" + calculateReversePolishNotation (result) + "\".");
  178. // System.out.println ();
  179.  
  180. return result;
  181. }
  182.  
  183. /**
  184.   * Выполнить вычисление в Обратной Польской Нотации.
  185.   *
  186.   * @param _exp
  187.   *
  188.   * @return Результат вычисления.
  189.   */
  190. public static double calculateReversePolishNotation (String _exp) throws CalculatorErrorException, CalculatorNaNException
  191. {
  192. // Стек. На нем будем вычислять.
  193. Stack<Double> stack = new Stack<Double> ();
  194.  
  195. // Грузим символы.
  196. String[] chars = _exp.split ("\\s");
  197.  
  198. for (String ch : chars)
  199. {
  200. double op1;
  201. double op2;
  202.  
  203. try
  204. {
  205. switch (ch)
  206. {
  207. // Бинарные операторы:
  208. case "+":
  209. op1 = stack.pop ();
  210. op2 = stack.pop ();
  211. stack.push (op2 + op1);
  212. break;
  213. case "-":
  214. op1 = stack.pop ();
  215. op2 = stack.pop ();
  216. stack.push (op2 - op1);
  217. break;
  218. case "*":
  219. op1 = stack.pop ();
  220. op2 = stack.pop ();
  221. stack.push (op1 * op2);
  222. break;
  223. case "/":
  224. op1 = stack.pop ();
  225. op2 = stack.pop ();
  226. stack.push (op2 / op1);
  227. break;
  228.  
  229. // Унарные операторы:
  230. case "^":
  231. op1 = stack.pop ();
  232. op2 = stack.pop ();
  233. stack.push (Math.pow (op2, op1));
  234. break;
  235. case "±":
  236. op1 = stack.pop ();
  237. stack.push (-1 * op1);
  238. break;
  239. case "sin":
  240. op1 = stack.pop ();
  241. stack.push (Math.sin (op1));
  242. break;
  243. case "cos":
  244. op1 = stack.pop ();
  245. stack.push (Math.cos (op1));
  246. break;
  247. case "tan":
  248. op1 = stack.pop ();
  249. stack.push (Math.tan (op1));
  250. break;
  251. case "ctg":
  252. op1 = stack.pop ();
  253. stack.push (1.0 / Math.tan (op1));
  254. break;
  255. case "ln":
  256. op1 = stack.pop ();
  257. stack.push (Math.log (op1));
  258. break;
  259.  
  260. default:
  261. if (!isNumber (ch))
  262. {
  263. throw new CalculatorErrorException ("CalculateRPN: \"" + ch + "\" - неверный формат операнда!");
  264. }
  265. else
  266. {
  267. stack.push (Double.valueOf (ch));
  268. }
  269. break;
  270. }
  271. }
  272. {
  273. throw new CalculatorErrorException ("CalculateRPN: Синтаксическая ошибка при вычислении RPN: неверный разделитель. Выражение: \"" + _exp + "\".");
  274. }
  275. }
  276.  
  277. double result = stack.pop ();
  278.  
  279. if (String.valueOf (result).equals ("NaN") || String.valueOf (result).equals ("Infinity") || String.valueOf (result).equals ("-Infinity"))
  280. {
  281. throw new CalculatorNaNException ("CalculateRPN: Математическая ошибка. Результат равен \"NaN/Infinity/-Infinity \".");
  282. }
  283.  
  284. return result;
  285. }
  286. //------------------------------------------------------------------------------------------------------------------
  287.  
  288. /**
  289.   * Получить приоритет оператора.
  290.   * <p>
  291.   * Операторы:
  292.   * Приоритет Оператор Ассоциативность
  293.   * 4 ! правая
  294.   * 3 * / % левая
  295.   * 2 + - левая
  296.   * 1 = левая
  297.   *
  298.   * @param _op
  299.   *
  300.   * @return
  301.   */
  302. protected static int getPriority (String _op)
  303. {
  304. switch (_op)
  305. {
  306. case "±":
  307. return 5;
  308. case "!":
  309. case "^":
  310. case "sin":
  311. case "cos":
  312. case "tan":
  313. case "ctg":
  314. case "ln":
  315. return 4;
  316. case "*":
  317. case "/":
  318. return 3;
  319. case "+":
  320. case "-":
  321. return 2;
  322. case "(":
  323. case ")":
  324. return 1;
  325. default:
  326. return -1;
  327. }
  328. }
  329.  
  330. protected static boolean isLeftAssociative (String _op)
  331. {
  332. switch (_op)
  333. {
  334. case "±":
  335. case "!":
  336. case "^":
  337. case "sin":
  338. case "cos":
  339. case "tan":
  340. case "ctg":
  341. case "ln":
  342. return false;
  343. default:
  344. case "*":
  345. case "/":
  346. case "+":
  347. case "-":
  348. return true;
  349. }
  350. }
  351.  
  352. /**
  353.   * Проверить правильность скобочной структуры.
  354.   *
  355.   * @param _exp Выражение со скобками.
  356.   *
  357.   * @return
  358.   */
  359. protected static boolean checkBracketsStructure (String _exp)
  360. {
  361. int number = -1;
  362. int a = 0;
  363.  
  364. while (++number < _exp.length ())
  365. {
  366. String ch = String.valueOf (_exp.charAt (number));
  367. if (ch.equals ("("))
  368. {
  369. ++a;
  370. }
  371. else if (ch.equals (")") && --a < 0)
  372. {
  373. break;
  374. }
  375. }
  376.  
  377. return (a == 0);
  378. }
  379.  
  380.  
  381. //------------------------------------------------------------------------------------------------------------------
  382.  
  383. /**
  384.   * Является ли эта строка числом? Точнее, является ли она допустимым оператором.
  385.   *
  386.   * @param _str
  387.   *
  388.   * @return
  389.   */
  390. protected static boolean isNumber (String _str)
  391. {
  392. return _str.matches (REGEX_DOUBLE);
  393. }
  394.  
  395. /**
  396.   * Является ли эта строка оператором?
  397.   *
  398.   * @param _str
  399.   *
  400.   * @return
  401.   */
  402. protected static boolean isOperator (String _str)
  403. {
  404. return _str.matches (REGEX_OPERATORS);
  405. }
  406.  
  407. /**
  408.   * Округлить число до заданных знаков после запятой.
  409.   *
  410.   * @param _num
  411.   * @param _scale
  412.   *
  413.   * @return
  414.   */
  415. protected static double round (double _num, int _scale)
  416. {
  417. BigDecimal bd = new BigDecimal (_num).setScale (_scale, RoundingMode.HALF_UP);
  418. return bd.doubleValue ();
  419. }
  420.  
  421. //------------------------------------------------------------------------------------------------------------------
  422.  
  423. /**
  424.   * Форматировать выражение в системный вид - с пробельным разделением всех операторов и операндов.
  425.   *
  426.   * @param _exp
  427.   *
  428.   * @return
  429.   */
  430. public static String formatExpToSystemView (String _exp)
  431. {
  432. return prepareOperatorsInString (_exp)
  433. .replaceAll ("([0-9])\\s*E\\s*-\\s*([0-9])", "$1E-$2")
  434.  
  435. .replaceAll ("\\s*([^ 0-9E\\)]+)\\s*-", " $1 ± ")
  436. .replaceAll ("^\\s*-\\s*([^-]+)", " ± $1 ")
  437.  
  438. .replaceAll ("\\s*\\(\\s*", " ( ")
  439. .replaceAll ("\\s*\\)\\s*", " ) ")
  440. .replaceAll ("\\s+", " ")
  441. .trim ();
  442. }
  443.  
  444. /**
  445.   * Расставить пробелы вокруг всех встреченных в строке операторов.
  446.   *
  447.   * @param _exp
  448.   *
  449.   * @return
  450.   */
  451. protected static String prepareOperatorsInString (String _exp)
  452. {
  453. String result = _exp;
  454. String[] ops = REGEX_OPERATORS.substring (2, REGEX_OPERATORS.length () - 2).split ("\\|");
  455.  
  456. for (String op : ops)
  457. {
  458. result = result.replaceAll ("\\s*" + op + "\\s*", " " + op + " ");
  459. }
  460.  
  461. return result;
  462. }
  463.  
  464. public static String formatExpToBeautifulView (String _exp)
  465. {
  466. return formatExpToSystemView (_exp)
  467. .replaceAll ("\\s*\\(\\s*", " (")
  468. .replaceAll ("\\s*\\)\\s*", ") ")
  469.  
  470. .replaceAll ("\\)\\s*\\)", "))")
  471. .replaceAll ("\\(\\s*\\(", "((")
  472.  
  473. .replaceAll (\\s*", "-");
  474. }
  475. }
  476.  
  477.  
  478. /* Name of the class has to be "Main" only if the class is public. */
  479. class Ideone
  480. {
  481. public static void main (String[] args) throws java.lang.Exception
  482. {
  483. try
  484. {
  485. String exp = "sin 0.4 - 2 * (3 / (ln tan 4) ^ cos 0.3)";
  486. System.out.println (Calculator.calculateNormal (exp));
  487. }
  488. catch (Calculator.CalculatorNaNException e)
  489. {
  490. e.printStackTrace ();
  491. }
  492. catch (Calculator.CalculatorErrorException e)
  493. {
  494. e.printStackTrace ();
  495. }
  496. }
  497. }
Success #stdin #stdout 0.05s 2184192KB
stdin
Standard input is empty
stdout
-37.189445145237336