摘要:
前几天要做一个计算数学表达式的题目,本来计划使用解析表达式的方法来解析各种数学表达式,然后再动态计算表达式的值.后来考虑到这样编程的任务很重,时间有限 后来在网上搜搜,看到使用动态编译并使用反射机制 ,这样计算表达式的编程就容易多了.
前几天要做一个计算数学表达式的题目,本来计划使用解析表达式的方法来解析各种数学表达式,然后再动态计算表达式的值.后来考虑到这样编程的任务很重,时间有限 后来在网上搜搜,看到使用动态编译并使用反射机制 ,这样计算表达式的编程就容易多了.下面是我这次编程的例子, 请大家看看.
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
这样就可以了.
说明:
使用该方法来计算本程序,由于要多次动态产生计算源代码,并且编译 在性能上会有很大损失. 要是在项目中不经常计算表达式 使用该方法可以减轻编程的负担.要是象上面那样 要多次计算的话,使用该方法是很值得考虑的.