Toán học mới với Java, Phần 1 : Các số thực Elliotte Rusty Harold ([email protected]) Giáo sư Polytechnic University 28 08 2009 Hãy tham gia cùng Elliotte Rusty Harold để xem những đặc trưng mới trong lớp java.lang.Math cổ điền trong bài báo gồm 2 phần này. Phần 1 tập trung chủ yếu vào các hàm toán học. Phần 2 sẽ khám phá những hàm được thiết kế cho các số dấu phảy động. Xem thêm bài trong loạt bài này Đôi khi bạn rất quen thuộc với một lớp đến mức mà bạn không để ý đến nó nữa. Nếu bạn có thể viết dẫn chứng tài liệu cho java.lang.Foo, và Eclipse sẽ tự động hoàn thành những hàm cho bạn, tại sao bạn lại phải cần đọc Javadoc của nó? Đó là kinh nghiệm mà tôi đã có với java.lang.Math, một lớp mà tôi nghĩ là tôi đã biết thực sự rõ. Hãy tưởng tượng sự ngạc nhiên của tôi, lúc đó khi tôi gần đây tình cờ được đọc Javadoc của nó sau gần suốt nửa thập kỷ và tôi nhận ra rằng lớp này đã gấp đôi về kích cỡ với 20 phương thức mới mà tôi chưa bao giờ nghe tới. Rõ ràng đó là lúc phải có một cái nhìn khác. Phiên bản 5 của Java™ Language Specification đã thêm 10 phương thức mới vào java.lang.Math (và người anh em của nó java.lang.StrictMath), và Java 6 đã thêm 10 phương thức khác nữa. Trong bài báo này, tôi tập trung vào những hàm toán học đơn giản hơn đã được cung cấp, ví dụ như là log10 và cosh. Trong phần 2, tôi sẽ khám phá những hàm khác nữa được thiết kế để hoạt động trên các số dấu phảy động đối lập với các số thực trừu tượng. Phân biệt giữa một số thực trừu tượng như là π hay 0.2 và một số double trong Java là điều rất quan trọng. Trước hết, mô hình lý tưởng Platonic của số là hoàn toàn chính xác, trong khi Java giới hạn một số lượng các bit cố định. Điều này rất quan trọng khi bạn xử lý các con số lẻ và lớn. Ví dụ, số 2.000.000.001 (hai tỉ lẻ 1) có thể được trình bày chính xác như một int, nhưng không phải như là một float. Điểm gần nhất mà bạn có thể đạt được trong một float là 2.0E9 — tức là 2 tỉ. Các double sẽ làm tốt hơn bởi vì chúng có nhiều số bit hơn (đó là lí do mà bạn nên luôn luôn sử dụng các double thay vì các float); nhưng vẫn có các giới hạn thực tế về độ chính xác của chúng. Giới hạn thứ 2 của số học máy tính (của ngôn ngữ Java và các ngôn ngữ khác) là ở chỗ nó được dựa trên hệ nhị phân hơn là hệ thập phân. Các phân số như là 1/5 và 7/50 mà có thể được trình bày chính xác trong hệ thập phân (lần lượt 0.2 và 0.14) trở thành các phân số lặp đi lặp lại khi được © Copyright IBM Corporation 2009 Toán học mới với Java, Phần 1: Các số thực Nhẫn hiệu đăng ký Trang 1 của 11 developerWorks® ibm.com/developerWorks/vn/ trình bày trong chú giải nhị phân. Điều này hoàn toàn giống với cách 1/3 trở thành 0.3333333... khi nó được trình bày dưới dạng thập phân. Trong cơ số 10, bất cứ phân số nào mà mẫu số có thừa số nguyên tố là 5 và 2 (chứ không phải là số khác) đều có thể trình bày được một cách chính xác. Trong cơ số 2, chỉ những phân số mà các phân số là lũy thừa của 2 thì có thể được trình bày chính xác là 1/2, 1/4, 1/8, 1/16 và tương tự. Những sự không chính xác là một trong những nguyên nhân lớn dẫn tới một lớp toán học được cần đến lúc ban đầu. Chắc chắn bạn có thể xác định lượng giác và các hàm khác với những mở rộng chuỗi Taylor không sử dụng bất cứ gì ngoài toán tử tiêu chuẩn + và * và một phép lặp đơn giản, như trong Ví dụ 1: Ví dụ 1. Tính các hàm sine với chuỗi Taylor public class SineTaylor { public static void main(String[] args) { for (double angle = 0; angle <= 4*Math.PI; angle += Math.PI/8) { System.out.println(degrees(angle) + "\t" + taylorSeriesSine(angle) + "\t" + Math.sin(angle)); } } public static double degrees(double radians) { return 180 * radians/ Math.PI; } public static double taylorSeriesSine(double radians) { double sine = 0; int sign = 1; for (int i = 1; i < 40; i+=2) { sine += Math.pow(radians, i) * sign / factorial(i); sign *= -1; } return sine; } private static double factorial(int i) { double result = 1; for (int j = 2; j <= i; j++) { result *= j; } return result; } } Ở đây, nó được bắt đầu khá tốt, có chăng cũng chỉ là một sự khác biệt nhỏ ở cuối dãy số thập phân: 0.0 22.5 45.0 67.5 90.0 0.0 0.0 0.3826834323650897 0.7071067811865475 0.923879532511287 1.0000000000000002 0.3826834323650898 0.7071067811865475 0.9238795325112867 1.0 Tuy nhiên, khi các góc bắt đầu tăng lên, các lỗi cũng sẽ bắt đầu nhiều và phương pháp tiếp cận này sẽ không còn hiệu quả nữa: Toán học mới với Java, Phần 1: Các số thực Trang 2 của 11 ibm.com/developerWorks/vn/ 630.0000000000003 652.5000000000005 675.0000000000005 697.5000000000006 -1.0000001371557132 -0.9238801080153761 -0.7071090807463408 -0.3826922100671368 developerWorks® -1.0 -0.9238795325112841 -0.7071067811865422 -0.3826834323650824 Chuỗi Taylor ở đây thực sự đã chứng minh được sự chính xác hơn tôi mong đợi. Tuy nhiên, khi góc tăng tới 360 độ, 720 độ (4 pi radian) hoặc cao hơn nữa, thì chuỗi Taylor yêu cầu càng nhiều số hạng để tính toán chính xác. Ngày càng nhiều thuật toán phức tạp được dùng bởi java.lang.Math để tránh được điều này. Chuỗi Taylor cũng không đạt hiệu quả so với hàm sine có sẵn của chip máy tính để bàn hiện đại ngày nay. Các phép tính riêng biệt của hàm sine và của các hàm khác nhanh và chính xác yêu cầu các thuật toán phải được thiết kế rất cẩn thận để tránh việc biến các lỗi nhỏ thành to. Thông thường, các thuật toán này được cài sẵn trong phần cứng để cải thiện tốc độ nhanh hơn.Ví dụ, hầu hết các chip X86 được bán ra trong vòng 10 năm qua đều có những bổ sung về phần cứng hàm sine và cosine mà thế hệ chip X86 VM chỉ việc gọi ra hơn là phải tính toán chậm chạp dựa trên các thao tác thô sơ ban đầu nữa. HotSpot lợi dụng những chỉ dẫn này để tăng đáng kể tốc độ các thao tác tính lượng giác. Các tam giác vuông và những tiên đề Ơclit Mọi học sinh phổ thông học hình học đều đã học về định lý Pytago: Bình phương chiều dài của cạnh huyền của một tam giác vuông bằng tổng bình phương chiều dài của hai cạnh góc vuông. Tức 2 2 2 là, c = a + b Những ai trong chúng ta đã từng áp dụng định lý đó vào vật lý trong đại học hoặc toán cao cấp đều biết rằng biểu thức này thể hiện được hơn rất nhiều điều chứ không chỉ dừng lại ở các tam giác 2 vuông. Thí dụ, cũng là bình phương trong tiên đề Ơclit về R , chiều dài của một vector hai chiều, một phần của bất đẳng thức tam giác, và hơn nữa. (Thực ra, đây là những cách nhìn khác nhau về cùng một vấn đề. Điểm quan trọng ở đây chính là tiên đề của Ơclit quan trọng hơn rất nhiều so với lúc xem xét nó ban đầu.) Java 5 đã thêm một hàm Math.hypot để thực hiện chính xác phép tính này, và đây là một ví dụ điển hình giải thích vì sao một thư viện là rất hữu ích. Cách tiếp cận này sẽ xem xét vấn đề như sau: public static double hypot(double x, double y){ return Math.sqrt (x*x + y*y); } Mã trình thực tế là một cái gì đó phức tạp hơn, như trong Ví dụ 2. Điều đầu tiên mà bạn sẽ chú ý đến đó là nó được viết bằng mã trình C gốc để đạt hiệu quả tối đa. Điều thứ 2 mà bạn cũng nên chú ý đó là nó đang đạt tới những độ dài lớn để cố gắng giảm tối thiểu bất cứ lỗi nào có thể trong phép tính này.Thực ra, các thuật toán khác nhau đang được chọn phụ thuộc vào kích thước tương đối của x và y. Ví dụ 2. Mã trình thực được thi hành Math.hypot /* Toán học mới với Java, Phần 1: Các số thực Trang 3 của 11 developerWorks® ibm.com/developerWorks/vn/ * ==================================================== * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. * * Developed at SunSoft, a Sun Microsystems, Inc. business. * Permission to use, copy, modify, and distribute this * software is freely granted, provided that this notice * is preserved. * ==================================================== */ #include "fdlibm.h" #ifdef __STDC__ double __ieee754_hypot(double x, double y) #else double __ieee754_hypot(x,y) double x, y; #endif { double a=x,b=y,t1,t2,y1,y2,w; int j,k,ha,hb; ha = __HI(x)&0x7fffffff; /* high word of x */ hb = __HI(y)&0x7fffffff; /* high word of y */ if(hb > ha) {a=y;b=x;j=ha; ha=hb;hb=j;} else {a=x;b=y;} __HI(a) = ha; /* a <- |a| */ __HI(b) = hb; /* b <- |b| */ if((ha-hb)>0x3c00000) {return a+b;} /* x/y > 2**60 */ k=0; if(ha > 0x5f300000) { /* a>2**500 */ if(ha >= 0x7ff00000) { /* Inf or NaN */ w = a+b; /* for sNaN */ if(((ha&0xfffff)|__LO(a))==0) w = a; if(((hb^0x7ff00000)|__LO(b))==0) w = b; return w; } /* scale a and b by 2**-600 */ ha -= 0x25800000; hb -= 0x25800000; k += 600; __HI(a) = ha; __HI(b) = hb; } if(hb < 0x20b00000) { /* b < 2**-500 */ if(hb <= 0x000fffff) { /* subnormal b or 0 */ if((hb|(__LO(b)))==0) return a; t1=0; __HI(t1) = 0x7fd00000; /* t1=2^1022 */ b *= t1; a *= t1; k -= 1022; } else { /* scale a and b by 2^600 */ ha += 0x25800000; /* a *= 2^600 */ hb += 0x25800000; /* b *= 2^600 */ k -= 600; __HI(a) = ha; __HI(b) = hb; } } /* medium size a and b */ w = a-b; if (w>b) { t1 = 0; __HI(t1) = ha; t2 = a-t1; w = sqrt(t1*t1-(b*(-b)-t2*(a+t1))); } else { a = a+a; y1 = 0; Toán học mới với Java, Phần 1: Các số thực Trang 4 của 11 ibm.com/developerWorks/vn/ developerWorks® __HI(y1) = hb; y2 = b - y1; t1 = 0; __HI(t1) = ha+0x00100000; t2 = a - t1; w = sqrt(t1*y1-(w*(-w)-(t1*y2+t2*b))); } if(k!=0) { t1 = 1.0; __HI(t1) += (k<<20); return t1*w; } else return w; } Thực ra, việc bạn kết thúc bằng một hàm riêng biệt hoặc một trong số một vài hàm khác tương tự như vậy phụ thuộc vào những chi tiết của JVM trên nền tảng của bạn. Tuy nhiên có nhiều khả năng rằng đây là mã trình được tạo ra trong tiêu chuẩn JDK của Sun.(Các thực thi khác của JDK nếu có thể sẽ được tự do cải tiến dựa trên tiêu chuẩn này.) Mã trình này (và hầu hết mã trình toán học gốc khác trong thư viện Java Development Library) đều có nguồn gốc từ thư viện mã nguồn mở fdlibm mà đã được viết ở Sun cách đây khoảng 15 năm. Thư viện này được thiết kế để thực thi dấu phảy động IEE754 một cách chính xác và có những phép tính chính xác, thậm chí phải hy sinh một tính hiệu quả nào đó. Logarit cơ số 10 Một logarit cho bạn biết lũy thừa nào một cơ số phải tăng lên để cho ra một giá trị đã định. Nghĩa là, nó là đảo ngược của hàm Math.pow(). Logarit cơ số 10 thường xuất hiện trong các ứng dụng kỹ nghệ. Logarit cơ số e (hay còn gọi là logarit tự nhiên) xuất hiện trong phép tính lợi ích chung, và nhiều ứng dụng toán học và khoa học khác. Logarit cơ số 2 thường xuất hiện trong phân tích thuật toán. Lớp Math đã có một hàm logarit tự nhiên từ phiên bản Java 1.0. Tức là, với một đối số x, hàm logarit tự nhiên sẽ trả lại lũy thừa mà cơ số e phải được tăng lên để cho giá trị x. Đáng buồn thay, hàm logarit tự nhiên của ngôn ngữ Java (và ngôn ngữ C, Fortran, và Basic) bị đặt tên sai là log(). Trong mọi sách giáo khoa toán mà tôi đã từng đọc, log là một hàm logarit cơ số 10, trong khi ln là một hàm logarit cơ số e và lg là một hàm logarit cơ số 2. Bây giờ đã quá muộn để sửa điều này, nhưng Java 5 đã thêm một hàm log10() mà lấy hàm logarit cơ số 10 thay vì cơ số e. Ví dụ 3 là một chương trình đơn giản để in hàm logarit cơ số 2, 10 và e của dãy số nguyên từ 1 đến 100: Toán học mới với Java, Phần 1: Các số thực Trang 5 của 11 developerWorks® ibm.com/developerWorks/vn/ Ví dụ 3. Các hàm logarit với nhiều cơ số khác nhau từ 1 đến 100 public class Logarithms { public static void main(String[] args) { for (int i = 1; i <= 100; i++) { System.out.println(i + "\t" + Math.log10(i) + "\t" + Math.log(i) + "\t" + lg(i)); } } public static double lg(double x) { return Math.log(x)/Math.log(2.0); } } Đât là 10 dòng đầu của kết quả: 1 2 3 4 5 6 7 8 9 10 0.0 0.3010299956639812 0.47712125471966244 0.6020599913279624 0.6989700043360189 0.7781512503836436 0.8450980400142568 0.9030899869919435 0.9542425094393249 1.0 0.0 0.0 0.6931471805599453 1.0 1.0986122886681096 1.584962500721156 1.3862943611198906 2.0 1.6094379124341003 2.321928094887362 1.791759469228055 2.584962500721156 1.9459101490553132 2.807354922057604 2.0794415416798357 3.0 2.1972245773362196 3.1699250014423126 2.302585092994046 3.3219280948873626 thông thường có những thông báo về các hàm logarit: lấy logarit của 0 hoặc bất kì một số âm nào sẽ trả lại giá trị NaN. Math.log10() Các phép căn bậc 3 Tôi không thể nói rằng tôi đã từng cần lấy căn bậc 3 trong đời tôi, và tôi là một trong số ít người sử dụng đại số học và hình học hàng ngày để đề cập đến những bước đột phá vào toán tích phân, vi phân, các phương trình vi phân, và thậm chí hư số học. Kết quả là, tính hữu dụng của hàm này đã không còn đối với tôi. Tuy nhiên, nếu bạn tìm ra một nhu cầu bất chợt nào để lấy căn bậc ba ở đâu đó, bạn bây giờ có thể dùng đến nó — như Java 5 — với phương pháp Math.cbrt() . Ví dụ 4 minh họa bằng cách lấy căn bậc 3 của dãy số nguyên từ -5 đến 5: Ví dụ 4. Căn bậc 3 từ -5 đến 5 public class CubeRoots { public static void main(String[] args) { for (int i = -5; i <= 5; i++) { System.out.println(Math.cbrt(i)); } } } Đây là kết quả: Toán học mới với Java, Phần 1: Các số thực Trang 6 của 11 ibm.com/developerWorks/vn/ developerWorks® -1.709975946676697 -1.5874010519681996 -1.4422495703074083 -1.2599210498948732 -1.0 0.0 1.0 1.2599210498948732 1.4422495703074083 1.5874010519681996 1.709975946676697 Như kết quả trên đã minh họa, một đặc trưng thú vị của căn bậc 3 so với căn bậc 2 là: Mỗi số thực có chính xác một căn bậc ba thực. Hàm này chỉ trả lại NaN khi đối số của nó là NaN. Các hàm lượng giác hypebol Các hàm lượng giác hypebol cho ra tương ứng các hypebol giống như các hàm lượng giác cho ra các hình tròn. Tức là, hãy tưởng tượng bạn vẽ những điểm này trên một mặt phẳng Đề-Các cho tất cả các giá trị có thể của t: x = r cos(t) y = r sin(t) Bạn sẽ vẽ ra được một vòng tròng với bán kính r. Ngược lại, giả sử bạn sử dụng thay thế bằng sinh và cosh, như sau: x = r cosh(t) y = r sinh(t) Bạn sẽ vẽ ra một hình hypebol chữ nhật có một điểm tiếp xúc gần nhất với gốc là r. ix -ix ix - Một cách khác: Chỗ sin(x) có thể được viết là (e - e )/2i và cos(x) có thể được viết là (e + e ix )/2 , sinh và cosh là cái mà bạn nhận được khi bạn gỡ bỏ đơn vị ảo từ các công thức đó. Tức là, x -x x -x sinh(x) = (e - e )/2 và cosh(x) = (e + e )/2. Java 5 thêm tất cả ba: Math.cosh(), Math.sinh(), và Math.tanh(). Các hàm lượng giác hypebol đảo ngược — acosh, asinh, và atanh — chưa được gộp vào. Về bản chất, cosh(z) là phương trình cho hình một chiếc dây treo được nối hai đầu, gọi là một dây xích (Catenary). Ví dụ 5 là một chương trình đơn giản vẽ hình một dây xích (Catenary) sử dụng hàm Math.cosh : Ví dụ 5. Vẽ một dây xích với Math.cosh() import java.awt.*; public class Catenary extends Frame { private private private private static static static static final final final final int WIDTH = 200; int HEIGHT = 200; double MIN_X = -3.0; double MAX_X = 3.0; Toán học mới với Java, Phần 1: Các số thực Trang 7 của 11 developerWorks® ibm.com/developerWorks/vn/ private static final double MAX_Y = 8.0; private Polygon catenary = new Polygon(); public Catenary(String title) { super(title); setSize(WIDTH, HEIGHT); for (double x = MIN_X; x <= MAX_X; x += 0.1) { double y = Math.cosh(x); int scaledX = (int) (x * WIDTH/(MAX_X - MIN_X) + WIDTH/2.0); int scaledY = (int) (y * HEIGHT/MAX_Y); // in computer graphics, y extends down rather than up as in // Caretesian coordinates' so we have to flip scaledY = HEIGHT - scaledY; catenary.addPoint(scaledX, scaledY); } } public static void main(String[] args) { Frame f = new Catenary("Catenary"); f.setVisible(true); } public void paint(Graphics g) { g.drawPolygon(catenary); } } Hình 1 thể hiện đường cong được vẽ: Hình 1. Một đường cong trong mặt phẳng Đề-Các Các hàm sinh, cosh, và tanh cũng xuất hiện trong các phép tính khác nhau trong tính tương đối riêng biệt và tổng quát. Đánh dấu Hàm Math.signum chuyển đổi các số dương thành 1.0, các số âm thành -1.0, và các số 0 thành 0. Thực chất, nó trích dấu từ một số. Điều này có thể sẽ hữu ích khi bạn đang thi hành giao diện Comparable. Có một phiên bản float và một phiên bản double để duy trì loại đó. Lí do cho hàm khá rõ ràng này là để nắm các trường hợp đặc biệt của toán học dấu phảy động, NaN, và số 0 dương và âm. NaN được coi như là số 0, số 0 dương và âm sẽ trả lại số 0 dương và âm. Ví dụ, giả sử bạn phải đơn thuần thi hành hàm này như trong Ví dụ 6: Toán học mới với Java, Phần 1: Các số thực Trang 8 của 11 ibm.com/developerWorks/vn/ developerWorks® Ví dụ 6. Lỗi thi hành của Math.signum public static double signum(double x) { if (x == 0.0) return 0; else if (x < 0.0) return -1.0; else return 1.0; } Đầu tiên, phương pháp này sẽ trả lại tất cả các số 0 âm thành các số 0 dương. (Đúng, các số 0 âm hơi kì lạ một chút, nhưng chúng là một phần cần thiết của thông số IEEE754.) Thứ hai, nó sẽ khẳng định rằng NaN là dương. Sự thi hành thực tế được thể hiện trong Ví dụ 7 phức tạp và cẩn thận hơn để nắm được các trường hợp góc kì lạ này: Ví dụ 7. Thi hành đúng thực tế của Math.signum public static double signum(double d) { return (d == 0.0 || isNaN(d))?d:copySign(1.0, d); } public static double copySign(double magnitude, double sign) { return rawCopySign(magnitude, (isNaN(sign)?1.0d:sign)); } public static double rawCopySign(double magnitude, double sign) { return Double.longBitsToDouble((Double.doubleToRawLongBits(sign) & (DoubleConsts.SIGN_BIT_MASK)) | (Double.doubleToRawLongBits(magnitude) & (DoubleConsts.EXP_BIT_MASK | DoubleConsts.SIGNIF_BIT_MASK))); } Làm ít, hưởng nhiều Mã trình hiệu quả nhất là mã trình mà bạn chưa bao giờ viết. Đừng làm theo những gì mà các chuyên gia đã làm. Mã trình sử dụng các hàm java.lang.Math , mới và cũ, sẽ nhanh hơn, hiệu quả hơn, và chính xác hơn bất cứ cái gì mà bạn tự viết. Hãy sử dụng nó. Toán học mới với Java, Phần 1: Các số thực Trang 9 của 11 developerWorks® ibm.com/developerWorks/vn/ Tài nguyên Học tập • "Java's new math, Part 2: Floating-point numbers" (Elliotte Rusty Harold, developerWorks, January 2008): Đừng quên phần đăng thứ 2 của loạt bài này, khám phá những hàm được thiết kế cho việc hoạt động trên các số dấu phảy động. • Types, Values, and Variables: Chương 4 của Java Language Specification bao quát số học dấu phảy động. • IEEE standard for binary floating-point arithmetic: Tiêu chuẩn IEEE 754 định nghĩa toán học dấu phảy động trong hầu hết các bộ xử lý và các ngôn ngũ, bao gồm ngôn ngữ Java. • java.lang.Math: Javadoc cho lớp cung cấp các hàm được thảo luận trong bài báo này. • Bug 5005861: Một người dùng thất vọng yêu cầu các hàm lượng giác nhanh hơn trong JDK. • Catenary: Wikipedia giải thích lịch sử và toán học đằng sau dây xích. • Duyệt technology bookstore cho các sách về những chủ đề kĩ thuật này và những chủ đề khác. • developerWorks Java technology zone: Tìm hàng trăm bài báo về mọi chủ đề lập trình Java. Lấy sản phẩm và công nghệ • fdlibm: Một thư viện toán học C cho máy hỗ trợ dấu phảy động IEEE754, có sẵn từ kho phần mềm toán học Netlib. • OpenJDK: Nhìn vào mã nguồn của các lớp toán bên trong thi hành Java SE mã nguồn mở. Thảo luận • Ghi tên developerWorks blogs và tham gia vào developerWorks community. Toán học mới với Java, Phần 1: Các số thực Trang 10 của 11 ibm.com/developerWorks/vn/ developerWorks® Đôi nét về tác giả Elliotte Rusty Harold Elliotte Rusty Harold xuất thân từ bang New Orleans nơi mà ông vẫn thỉnh thoảng về thăm những lúc thảnh thơi. Tuy nhiên, ông đang cư trú gần Trung tâm University Town Center, Irvine cùng với vợ ông là Beth và những chú mèo Charm (được đặt tên theo hạt "charm quark" trong vật lý) và Marjorie (đặt tên theo tên của mẹ vợ ông). Trang Web Cafe au Lait của ông đã trở thành một trong những trang Java độc lập nổi tiếng nhất trên Internet, và trang Web phụ của ông, Cafe con Leche, đã trở thành một trong những trang XML phổ biến nhất. Cuốn sách của ông gần đây nhất là Refactoring HTML © Copyright IBM Corporation 2009 (www.ibm.com/legal/copytrade.shtml) Nhẫn hiệu đăng ký (www.ibm.com/developerworks/vn/ibm/trademarks/) Toán học mới với Java, Phần 1: Các số thực Trang 11 của 11