摘要:

前几天要做一个计算数学表达式的题目,本来计划使用解析表达式的方法来解析各种数学表达式,然后再动态计算表达式的值.后来考虑到这样编程的任务很重,时间有限 后来在网上搜搜,看到使用动态编译并使用反射机制 ,这样计算表达式的编程就容易多了.

前几天要做一个计算数学表达式的题目,本来计划使用解析表达式的方法来解析各种数学表达式,然后再动态计算表达式的值.后来考虑到这样编程的任务很重,时间有限 后来在网上搜搜,看到使用动态编译并使用反射机制 ,这样计算表达式的编程就容易多了.下面是我这次编程的例子, 请大家看看.


01 /*
02  * Created on 2006-3-8
03  * @author icerain 我的Blog: 
http://blog.matrix.org.cn/page/icess
04  
*/

05 
06 public interface IOperator {
07   String SIN = "sin";
08   String COS = "cos";
09   String TAN = "tan";
10   String ASIN = "asin";
11   String ACOS = "acos";
12   String ATAN = "atan";
13   String EXP = "exp";
14   String LOG = "log";
15   String POW = "pow";
16   String SQRT = "sqrt";
17   String FABS = "fabs";
18   String MINUS = "minus";
19   
20   String J_SIN = "Math.sin";
21   String J_COS = "Math.cos";
22   String J_TAN = "Math.tan";
23   String J_ASIN = "Math.asin";
24   String J_ACOS = "Math.acos";
25   String J_ATAN = "Math.atan";
26   String J_EXP = "Math.exp";
27   String J_LOG = "Math.log10";
28   String J_POW = "Math.pow";
29   String J_SQRT = "Math.sqrt";
30   String J_FABS = "Math.abs";
31   
32 }
 

定义一个接口, 用来转换各种数学符号为Java类库中的表达式.

下面是用来计算的代码.

001 /*
002  * Created on 2006-3-7
003  * @author icerain 我的Blog: 
http://blog.matrix.org.cn/page/icess
004  
*/

005 //package hust.icess.simpson;
006 
007 
008 import java.util.logging.Level;
009 
010 import java.io.*;
011 import java.lang.reflect.Method;
012 import java.util.Scanner;
013 import java.util.logging.Logger;
014 
015 
016 import com.sun.tools.javac.*;
017 /**
018  * 利用Simpson公式计算积分,在输入被积公式时候请注意使用如下格式.
019  * 1.只使用圆括号() , 没有别的括号可以使用.如: 1/(1+sin(x))
020  * 2.在输入超越函数的时候,变量和数值用括号扩起来 如:sin(x) 而不要写为 sinx
021  * 3.在两个数或者变量相乘时候,不要省略乘号* 如:2*a 不要写为 2a
022  * 4.在写幂运算的时候,请使用如下格式: 
023  * 利用动态编译来计算Simpson积分,使用该方法 编程相对简单,运行效率有点慢.
024  * 
@author icerain
025  *
026  
*/

027 public class Simpson implements IOperator {
028   /**
029    * Logger for this class
030    
*/

031   private static final Logger logger = Logger.getLogger(Simpson.class
032       .getName());
033 
034   private String expression = null;
035 
036   private String variable = null;
037 
038   private String[] variableValue = new String[3];
039 
040   // private static Main javac = new Main();
041 
042   /**主函数 */
043   public static void main(String[] args) throws Exception {
044     Simpson sim = new Simpson();
045     System.out.println("结果如下:");
046     System.out.print(sim.getSimpsonValue());
047     System.exit(0);
048 
049   }

050 
051   public Simpson() {
052     logger.setLevel(Level.WARNING);
053     init();
054   }

055 
056   /** 初始化用户输入,为技术Simpson积分做准备. */
057   private void init() {
058     Scanner scanner = new Scanner(System.in);
059     System.out.println("请输入函数表达式 如 1+sin(a) + cos(a)/a :");
060     // String input = scanner.nextLine();
061     //读入被积函数的表达式
062     expression = scanner.nextLine().trim().toLowerCase();
063     System.out.println("请输入变量字符 如 a :");
064     //读入变量字符
065     variable = scanner.nextLine().trim().toLowerCase();
066     
067     //处理多元函数 目前不实现该功能
068     // String[] tempVars = tempVar.split(" ");
069     // for(int i = 0; i < tempVars.length; i ++) {
070     // variable[i] = tempVars[i];
071     // }
072 
073     System.out.println("请输入积分区间和结点数 如 2 5.4 10 :");
074     //读取复合Simpson公式的积分参数
075     String tempValue = scanner.nextLine().trim();
076     String[] tempValues = tempValue.split(" ");
077     for (int i = 0; i < tempValues.length; i++{
078       variableValue[i] = tempValues[i];
079     }

080 
081   }

082 
083   /** 计算 Simpson积分的值*/
084   public double getSimpsonValue() {
085     //保存中间结果
086     double value1 = 0;
087     double value2 = 0;
088     double tempValue = 0;
089     int i = 0;
090     // 解析输入的积分参数值
091     int n = Integer.parseInt(variableValue[2]);
092     double a = Double.parseDouble(variableValue[0]);
093     double b = Double.parseDouble(variableValue[1]);
094     double h = (b - a) / n;
095     //计算value1
096     for (i = 0; i < n; i++{
097       tempValue = a + (i + 0.5* h;
098       String code = getSourceCode(expression, getVariable(), Double
099           .toString(tempValue));
100       try {
101         value1 += run(compile(code));
102       }
 catch (Exception e) {
103         // TODO Auto-generated catch block
104         e.printStackTrace();
105 
106         if (logger.isLoggable(Level.INFO)) {
107           logger.info("something is wrong");
108         }

109       }

110     }

111     //计算value2
112     for (i = 1; i < n; i++{
113       tempValue = a + i * h;
114       String code = getSourceCode(expression, getVariable(), Double
115           .toString(tempValue));
116       try {
117         value2 += run(compile(code));
118       }
 catch (Exception e) {
119         // TODO Auto-generated catch block
120         e.printStackTrace();
121         if (logger.isLoggable(Level.INFO)) {
122           logger.info("something is wrong");
123         }

124       }

125     }

126 
127     //计算f(a) f(b) 的函数值
128     double valueA = getFunctionValue(a);
129     double valueB = getFunctionValue(b);
130     //计算Simpson公式的值
131     double resultValue = (valueA + valueB + 4 * value1 + 2 * value2) * h / 6;
132     
133     return resultValue;
134   }

135 
136   //计算F(a) 的值
137   private double getFunctionValue(double varValue) {
138     String code = getSourceCode(expression, getVariable(), Double
139         .toString(varValue));
140     double result = 0;
141     try {
142       result = run(compile(code));
143     }
 catch (Exception e) {
144       // TODO Auto-generated catch block
145       e.printStackTrace();
146       if (logger.isLoggable(Level.INFO)) {
147         logger.info("something is wrong");
148       }

149     }

150     return result;
151   }

152 
153   /** 
154    * 得到用户输入表达式转换为Java中的可计算表达式的函数
155    * 
@param ex 输入的表达式 如: 1/(1 + sin(x)) 
156    * 
@param var 表达式中的变量 如: x
157    * 
@param value 变量的取值 如: 4.3
158    * 
@return Java中可以直接计算的表达式 如: 1/(1 + Math.sin(x))
159    
*/

160   private String getSourceCode(String ex, String var, String value) {
161     String expression = ex;
162     //计算多个变量的函数的时候使用
163     
164     expression = expression.replaceAll(var, value);
165     
166     //处理数学符号
167     if (expression.contains(SIN)) {
168       expression = expression.replaceAll(SIN, J_SIN);
169     }
 else if (expression.contains(COS)) {
170       expression = expression.replaceAll(COS, J_COS);
171     }
 else if (expression.contains(TAN)) {
172       expression = expression.replaceAll(TAN, J_TAN);
173     }
 else if (expression.contains(ASIN)) {
174       expression = expression.replaceAll(ASIN, J_ASIN);
175     }
 else if (expression.contains(ACOS)) {
176       expression = expression.replaceAll(ACOS, J_ACOS);
177     }
 else if (expression.contains(ATAN)) {
178       expression = expression.replaceAll(ATAN, J_ATAN);
179     }
 else if (expression.contains(EXP)) {
180       expression = expression.replaceAll(EXP, J_EXP);
181     }
 else if (expression.contains(LOG)) {
182       expression = expression.replaceAll(LOG, J_LOG);
183     }
 else if (expression.contains(POW)) {
184       expression = expression.replaceAll(POW, J_POW);
185     }
 else if (expression.contains(SQRT)) {
186       expression = expression.replaceAll(SQRT, J_SQRT);
187     }
 else if (expression.contains(FABS)) {
188       expression = expression.replaceAll(FABS, J_FABS);
189     }

190 
191     return expression;
192   }

193 
194   /** 编译JavaCode,返回java文件*/
195   private synchronized File compile(String code) throws Exception {
196     File file;
197     // 创建一个临时java源文件
198     file = File.createTempFile("JavaRuntime"".java"new File(System
199         .getProperty("user.dir")));
200     if (logger.isLoggable(Level.INFO)) {
201       logger.info(System.getProperty("user.dir"));
202     }

203     // 当Jvm 退出时 删除该文件
204      file.deleteOnExit();
205     // 得到文件名和类名
206     String filename = file.getName();
207     if (logger.isLoggable(Level.INFO)) {
208       logger.info("FileName: " + filename);
209     }

210     String classname = getClassName(filename);
211     // 将代码输出到源代码文件中
212     PrintWriter out = new PrintWriter(new FileOutputStream(file));
213     // 动态构造一个类,用于计算
214     out.write("public class " + classname + "{"
215         + "public static double main1(String[] args)" + "{");
216     out.write("double result = " + code + ";");
217     //用于调试
218     //out.write("System.out.println(result);");
219     out.write("return new Double(result);");
220     out.write("}}");
221     //关闭文件流
222     out.flush();
223     out.close();
224     //设置编译参数
225     String[] args = new String[] "-d", System.getProperty("user.dir"),
226         filename }
;
227     //调试
228     if (logger.isLoggable(Level.INFO)) {
229       logger.info("编译参数: " + args[0]);
230     }

231     //Process process = Runtime.getRuntime().exec("javac " + filename);
232     int status = Main.compile(args);
233     //输出运行的状态码.
234     //    状态参数与对应值 
235     //      EXIT_OK 0 
236     //      EXIT_ERROR 1 
237     //      EXIT_CMDERR 2 
238     //      EXIT_SYSERR 3 
239     //      EXIT_ABNORMAL 4
240     if (logger.isLoggable(Level.INFO)) {
241       logger.info("Compile Status: " + status);
242     }

243     //System.out.println(process.getOutputStream().toString());
244     return file;
245   }

246 
247   /**
248    * 运行程序 如果出现Exception 则不做处理 抛出!
249    * 
@param file 运行的文件名
250    * 
@return 得到的Simpson积分公式的结果
251    * 
@throws Exception 抛出Exception 不作处理
252    
*/

253   private synchronized double run(File file) throws Exception {
254     String filename = file.getName();
255     String classname = getClassName(filename);
256     Double tempResult = null;
257     // System.out.println("class Name: " +classname);
258     //当Jvm 退出时候 删除生成的临时文件
259     new File(file.getParent(), classname + ".class").deleteOnExit();
260     try {
261       Class cls = Class.forName(classname);
262       //System.out.println("run..");
263       // 映射main1方法
264       Method calculate = cls
265           .getMethod("main1"new Class[] { String[].class });
266       //执行计算方法 得到计算的结果
267       tempResult = (Double) calculate.invoke(null,
268           new Object[] new String[0] });
269     }
 catch (SecurityException se) {
270       System.out.println("something is wrong !!!!");
271       System.out.println("请重新运行一遍");
272     }

273     //返回值
274     return tempResult.doubleValue();
275   }

276 
277   /** 调试函数*/
278   // private void debug(String msg) {
279   // System.err.println(msg);
280   // }
281 
282   /** 得到类的名字 */
283   private String getClassName(String filename) {
284     return filename.substring(0, filename.length() - 5);
285   }

286 
287   
288   //getter and setter
289   public String getExpression() {
290     return expression;
291   }

292 
293   public void setExpression(String expression) {
294     this.expression = expression;
295   }

296 
297   public String getVariable() {
298     return variable;
299   }

300 
301   public void setVariable(String variable) {
302     this.variable = variable;
303   }

304 
305   public String[] getVariableValue() {
306     return variableValue;
307   }

308 
309   public void setVariableValue(String[] variableValue) {
310     this.variableValue = variableValue;
311   }

312 }
 

这样就可以用来计算了.

下面编写一个.bat文件来运行改程序.(在这里没有打包为.jar文件)

@echo 注意:
@echo ***********************************************************
@echo * 利用Simpson公式计算积分,在输入被积公式时候请注意使用 ***
@echo * 如下格式. ***
@echo * 1.只使用圆括号() , 没有别的括号可以使用.如: ***
@echo * 1/(1+sin(x)) ***
@echo * 2.在输入超越函数的时候,变量和数值用括号扩起来 如: ***
@echo * sin(x) 而不要写为 sinx ***
@echo * 3.在两个数或者变量相乘时候,不要省略乘号* 如: ***
@echo * 2*a 不要写为 2a ***
@echo * 4.在写幂运算的时候,请使用如下格式: ***
@echo * pow(x,y) 代表x的y次幂 不要使用其他符号 ***
@echo * 5.绝对值请用如下符号表示: ***
@echo * fabs(x) 代表x的绝对值 ***
@echo * 6.指数函数请用exp表示 如:exp(x) ***
@echo * 7.对数函数请用log(x)表示, 该处对数是指底为10的对数, ***
@echo * 计算不是以10为底的对数时候请转换为10为底的对数 ***
@echo * 8.变量字符请不要与函数中的其他字符重合,如 如果使用了 ***
@echo * sin 函数请 不要用 s i 或者n做为变量,否则在解析 ***
@echo * 表达式时候 会出错 ^_^
@echo ***********************************************************

@Rem 在编译源文件时候 要使用下面的命令 把rem 删除即可 注意 由于文件中用到了tools.jar中
@rem 的命令 所有在编译的时候 用适当的classpath 替换下面的 tools.jar的路径 运行的时候一样

@rem javac -classpath ".;D:\Program Files\Java\jdk1.5.0_03\lib\tools.jar;%CLASSPATH%" Simpson.java %1

@rem 注意更改此处的tools.jar的路径 为你当前系统的正确路径
@java -cp ".;D:\Program Files\Java\jdk1.5.0_03\lib\tools.jar" Simpson

@Pause

 

这样就可以了.

说明:

使用该方法来计算本程序,由于要多次动态产生计算源代码,并且编译 在性能上会有很大损失. 要是在项目中不经常计算表达式 使用该方法可以减轻编程的负担.要是象上面那样 要多次计算的话,使用该方法是很值得考虑的.