Các hàm toán học

Một phần của tài liệu ThongDich ppsx (Trang 28 - 37)

Như đã trù hoạch, phiên bản Lan Hoa sẽ hỗ trợ một số hàm toán học. Nhưng làm cách nào gọi các hàm này khi tính toán biểu thức? Câu trả lời sẽ rất thú vị.

Thứ nhất, hàm toán học sẽ được xem như toán tử đơn phân. Nó giống với dấu âm ở chỗ chỉ tác động lên toán hạng đứng bên phải, và khác ở chỗ toán hạng này phải được bao trong cặp dấu ngoặc.

Thứ nhì là cách thực hiện các hàm. Theo khuynh hướng thông thường thì ta sẽ dùng lệnh rẽ nhánh switchdựa theo thành phần IntValue của token. Đại loại như:

Syntax: [ Download ] [ Hide ] [ Select ] [ Contract ]

Using csharp Syntax Highlighting

switch (tmp.IntValue) {

res.Push(Math.Sqrt(res.Pop()));

case 1: //abs

res.Push(Math.Abs(res.Pop()));

// ...

}

Parsed in 0.007 seconds, using GeSHi 1.0.8.4

Nhưng không. Cách này tuy xài được, nhưng kém linh hoạt. Sau này nếu cần thêm các hàm mới, chẳng lẽ lại rẽ nhánh tiếp? Đó là chưa kể việc này làm giảm hiệu suất chương trình và tăng sự rối rắm của mã lệnh.

Chúng ta sẽ tận dụng sự hỗ trợ quý giá của môi trường .NET để áp dụng kỹ thuật "bảng rẽ nhánh" (branch table). Kỹ thuật này, nói một cách đơn giản và trong trường hợp hiện tại, là tạo một mảng các hàm và gọi các hàm này qua số chỉ mục tương ứng trong IntValue.

Nếu C/C++ có con trỏ hàm thì .NET có ủy thác hàm (delegate). Hy vọng bạn không xa lạ lắm đối với khái niệm này, vì sẽ cần nhiều thời gian để giải thích tường tận.

Ta cần định nghĩa một khuôn mẫu hàm như sau:

Syntax: [ Download ] [ Hide ]

Using csharp Syntax Highlighting

private delegate double FuncDelegate(double d);

Parsed in 0.006 seconds, using GeSHi 1.0.8.4

Và một mảng các hàm (bảng rẽ nhánh):

Syntax: [ Download ] [ Hide ]

Using csharp Syntax Highlighting

private FuncDelegate[] FuncTable;

Parsed in 0.006 seconds, using GeSHi 1.0.8.4

Ta sẽ lần lượt viết từng hàm với khuôn mẫu đã định nghĩa ở trên và đưa chúng vào mảng FuncTable trong hàm tạo lập của Expression.

Thí dụ:

Syntax: [ Download ] [ Hide ]

Using csharp Syntax Highlighting

private double f_sqrt(double d) {

return Math.Sqrt(d);

}

Parsed in 0.006 seconds, using GeSHi 1.0.8.4

Và khi gọi hàm thì ta có:

Syntax: [ Download ] [ Hide ]

Using csharp Syntax Highlighting

res.Push(FuncTable[tmp.IntValue](res.Pop()));

c. Viết mã lệnh

Người xưa nói: văn kỳ thinh bất kiến kỳ hình. Ở đây là chỉ nghe diễn tả mà chưa ngó mã lệnh thì cũng tù mù khó hình dung. Do đó, mời bạn xem mã lệnh của lớp Expression:

Syntax: [ Download ] [ Hide ] [ Select ] [ Contract ]

Using csharp Syntax Highlighting

using System;

using System.Collections.Generic;

namespace BieuThuc.LanHoa {

class Expression {

#region Các lớp hỗ trợ

private Parser mParser;

private Lexer mLexer;

private Error mError;

#endregion

#region Các hàm toán học

private delegate double FuncDelegate(double d);

private FuncDelegate[] FuncTable;

private double f_sqrt(double d) {

return Math.Sqrt(d);

}

private double f_abs(double d) {

return Math.Abs(d);

}

private double f_sin(double d) {

return Math.Sin(d);

}

private double f_cos(double d) {

return Math.Cos(d);

}

private double f_tan(double d) {

return Math.Tan(d);

}

private double f_asin(double d) {

return Math.Asin(d);

}

private double f_acos(double d) {

return Math.Acos(d);

}

private double f_atan(double d) {

return Math.Atan(d);

}

#endregion

public Expression() {

// Khởi lập các lớp

this.mError = new Error();

this.mLexer = new Lexer(this.mError);

this.mParser = new Parser(this.mLexer, this.mError);

// Tạo bảng ủy thác các hàm

// Lưu ý: thứ tự các hàm trong bảng này

// phải đúng y thứ tự trong mảng FuncIdent

// thuộc lớp Lexer.

this.FuncTable = new FuncDelegate[] {

f_sqrt, f_abs, f_sin, f_cos, f_tan, f_asin, f_acos, f_atan };

}

public bool Evaluate(string expr, ref double result) {

// Danh sách token

List<Lexer.Token> list = new List<Lexer.Token>();

// Danh sách token sau khi chuyển sang RPN

List<Lexer.Token> rpn = new List<Lexer.Token>();

Stack<double> res = new Stack<double>();

Lexer.Token tmp = new Lexer.Token();

double a = 0, b = 0;

// Thanh lọc biểu thức

if (mParser.Parse(expr, ref list) == false) return false;

// Chuyển sang RPN

ConvertToRPN(list, ref rpn);

for (int i = 0; i < rpn.Count; ++i) {

tmp = rpn[i ];

switch (tmp.Class) {

case Lexer.TokenClass.NumInt:

res.Push(tmp.IntValue);

break;

case Lexer.TokenClass.NumFloat:

res.Push(tmp.FloatValue);

break;

case Lexer.TokenClass.Plus:

b = res.Pop();

a = res.Pop();

res.Push(a + b);

break;

case Lexer.TokenClass.Minus:

b = res.Pop();

a = res.Pop();

res.Push(a - b);

break;

case Lexer.TokenClass.Mul:

b = res.Pop();

a = res.Pop();

res.Push(a * b);

break;

case Lexer.TokenClass.Div:

b = res.Pop();

a = res.Pop();

if (b == 0) {

mError.RaiseError(Error.ErrorConstant.DivisionByZero);

return false;

} else

res.Push(a / b);

} break;

case Lexer.TokenClass.Mod:

b = res.Pop();

a = res.Pop();

if (b == 0) {

mError.RaiseError(Error.ErrorConstant.DivisionByZero);

return false; } else { res.Push(a % b); } break;

case Lexer.TokenClass.Pow:

b = res.Pop();

a = res.Pop();

res.Push(Math.Pow(a, b));

break;

case Lexer.TokenClass.UMinus:

res.Push(-res.Pop());

break;

case Lexer.TokenClass.Func:

res.Push(FuncTable[tmp.IntValue](res.Pop()));

break;

} //switch

} //while

result = res.Pop();

return true;

}

private void ConvertToRPN(List<Lexer.Token> list, ref List<Lexer.Token> rpn) {

// Ngăn xếp tạm chứa toán tử

Stack<Lexer.Token> opr = new Stack<Lexer.Token>();

Lexer.Token tok = new Lexer.Token();

// Vòng lặp chuyển đổi

for (int i = 0; i < list.Count; ++i) {

tok = list<span style="font-style: italic">;

switch (tok.Class) {

case Lexer.TokenClass.NumInt:

case Lexer.TokenClass.NumFloat:

rpn.Add(tok);

break;

case Lexer.TokenClass.LPar:

opr.Push(tok);

break;

case Lexer.TokenClass.RPar:

while (opr.Count != 0 && opr.Peek().Class ! =Lexer.TokenClass.LPar)

rpn.Add(opr.Pop());

opr.Pop(); //LPar

break;

case Lexer.TokenClass.Plus:

case Lexer.TokenClass.Minus:

case Lexer.TokenClass.UMinus:

case Lexer.TokenClass.Mul:

case Lexer.TokenClass.Div:

case Lexer.TokenClass.Mod:

case Lexer.TokenClass.Pow:

case Lexer.TokenClass.Func:

while (opr.Count ! = 0 && opr.Peek().Precedence()>= tok.Precedence())

rpn.Add(opr.Pop());

opr.Push(tok);

break;

} }

// Xổ các toán tử còn lại vào danh sách RPN

while (opr.Count != 0) rpn.Add(opr.Pop());

} }

}

Parsed in 0.039 seconds, using GeSHi 1.0.8.4

6. Báo lỗi

Ta không chỉ bắt lỗi. Ta còn cần phải báo các lỗi đó cho người sử dụng biết. Chớ mà cứ bắt lỗi rồi im ru thì không giống con giáp nào hết.

Vậy nên mới có lớp [i]Error.

Nếu tinh mắt, bạn sẽ thấy trong các lớp ở trên, có một số lời gọi hàm RaiseError với đối số là một hằng thuộc kiểu ErrorConstant.

Trong hàm RaiseError có gì? Rất dễ nhận ra rằng trong đó có các lệnh tạo chuỗi báo lỗi và gọi hàmMessageBox.Show. Vậy, một hằng ErrorConstant chuyển thành một chuỗi thông báo lỗi bằng

cách nào?

Cách dễ là switch. Còn cách "đẹp" thì giống như trường hợp của các hàm toán học. Tức là sẽ có một mảng các thông báo mà chỉ mục của chúng tương ứng chính xác với giá trị của các hằng lỗi. Đây là mã lệnh của lớp Error:

Syntax: [ Download ] [ Hide ] [ Select ] [ Contract ]

Using csharp Syntax Highlighting

using System.Windows.Forms;

namespace BieuThuc.LanHoa {

class Error {

// Liệt kê các loại lỗi

public enum ErrorConstant { InvalidCharacter, UndefinedFunction, ExpectLPar, ExpectRPar, ExpectExpression, ExpectExprRest, DivisionByZero }

// Nội dung các thông báo lỗi

private string[] ErrorConstantStr;

public Error() {

this.ErrorConstantStr = new string[] {

"Ký tự lạ.",

"Hàm chưa định nghĩa.", "Thiếu dấu mở ngoặc.", "Thiếu dấu đóng ngoặc.", "Không nhận được biểu thức.",

"Không nhận được phần sau của biểu thức.", "Phép chia cho số 0."

};

}

// Hàm báo lỗi

public void RaiseError(ErrorConstant e) {

string err = "Lỗi " + ((int)e).ToString("D2") + ":\n";

MessageBox.Show(err, "Loi", MessageBoxButtons.OK, MessageBoxIcon.Error);

} }

}

Parsed in 0.013 seconds, using GeSHi 1.0.8.4

7. Kết luận

Mã lệnh cho phần giao diện không khác phiên bản trước. Chúng ta cứ chép lại và chạy thử. Lần này, thử nhập một biểu thức sai cú pháp. Chương trình sẽ nhanh nhảu lên tiếng báo lỗi.

Vậy là xong. Lan Hoa đã hoàn thành và thi hành ngon lành. Kể cũng không uổng mấy tiếng đồng hồ hì hục nghiên cứu!

Nhưng chúng ta sẽ còn gặp nhau. Bởi phiên bản 2.0 chưa thấm vào đâu so với phiên bản 3.0 sắp tới. Liên Hoa sẽ mang đến nhiều bất ngờ lý thú. Và, xin tiết lộ một chút, chúng ta sắp sửa viết được một chương trình thông dịch.

Tham khảo http://en.wikipedia.org/wiki/Parsing http://en.wikipedia.org/wiki/Recursive_descent_parser http://en.wikipedia.org/wiki/Top-down_parsing http://en.wikipedia.org/wiki/Branch_table TẬP TIN ĐÍNH KÈM LanHoa.zip

Biểu Thức 2.0 - Lan Hoa

(58.94 KiB) Đã tải về 85 lần

Sửa lần cuối bởi alexanderdna vào ngày Thứ 4 12/01/2011 8:55 am với 1 lần sửa trong tổng số. Bổn quốc tự kim cổ,

Việt tộc dựng cơ đồ.

Bắc phương sao cuồng vọng? Sẽ thấy tự chuốc khổ!

alexanderdna

Guru

Bài viết: 193

Ngày tham gia: Thứ 3 14/07/2009 11:13 am

Đến từ: Sài Gòn

Được cảm ơn:6 lần Đ ầ u t r a n g

Một phần của tài liệu ThongDich ppsx (Trang 28 - 37)

Tải bản đầy đủ (DOCX)

(98 trang)
w