วันพุธที่ 5 สิงหาคม พ.ศ. 2558

การคำนวณเชิงตัวเลข (๗) ใช่ว่าคอมพิวเตอร์จะคิดเลขถูกเสมอไป MO Memoir : Wednesday 5 August 2558

ผมเรียนการคำนวณเชิงตัวเลขด้วยการใช้คอมพิวเตอร์ในยุคสมัยที่หน่วยความจำของคอมพิวเตอร์นั้นมีจำกัดมาก ตอนที่คำนวณบนเครื่อง mainframe หรือ mini ก็ไม่ค่อยมีปัญหามากนัก ที่มีปัญหามากกว่าคือเมื่อย้ายโปรแกรมมาทำงานบนเครื่อง workstation หรือ micro computer โดยเฉพาะ micro computer ที่ใช้ระบบปฏิบัติการ DOS ที่มองเห็นหน่วยความจำแค่ 1 MB แต่ใน 1 MB นี้ DOS ยึดไปก่อนแล้ว 384 kB (ช่วงเหนือ 640 kB - 1024 kB หน่วยความจำส่วนนี้เรียกว่า upper memory area) เหลือให้โปรแกรมต่าง ๆ ใช่เพียงแค่ 640 kB (ช่วง 0-640 kB ที่เรียกว่า conventional memory) ซึ่งใน 640 kB นี้หลังจากที่เปิดเครื่องและคอมมันโหลด driver ต่างๆ เข้าไปแล้ว ถ้าปรับแต่งดี ๆ ก็อาจได้หน่วยความจำที่ใช้เป็นที่เก็บข้อมูลในการคำนวณได้สัก 200-300 kB (1 MB เท่ากับ 1024 kB นะ) แต่ตอนหลังพอหน่วยความจำมีราคาถูกลง ก็เลยมีความพยายามจะให้ DOS เรียกใช้หน่วยความจำที่อยู่สูงเกินกว่า 1 MB ได้ ตรงนี้ก็เลยมีการจัดหน่วยความจำโดยแบ่งออกเป็น high memory area, extended memory และ expanded memory (แค่นี้ก็ทำให้ผู้ใช้งานงงไปหมดแล้ว)
  
ชิปสำหรับไมโครคอมพิวเตอร์ตระกูล Intel ที่เริ่มจาก 8086 หรือ 8088 นั้น เป็นชิปสำหรับใช้งานประมวลผลทั่วไป ถ้าต้องการเน้นการคำนวณเป็นหลัก จะต้องซื้อชิปเพิ่มเติมที่เรียกว่า Math co-processor ติดตั้งเพิ่มเติม ชิปตัวนี้ลงท้ายด้วยเลข 7 คือ 8087, 80287 และ 80387 แต่พอ Intel ออกซีพียูรุ่น 80486 กลับไม่มีชิป 80487 แต่ใช้วิธีให้ผู้ใช้เลือกว่าจะซื้อซีพียู 80486 ชนิดที่มีการรวมฟังก์ชัน Math co-processor เอาไว้ในตัว (รุ่น 486DX) หรือรุ่นที่ไม่มีฟังก์ชันนี้ (486SX)
  
ด้วยเหตุนี้เวลาที่เราเขียนโปรแกรมเสร็จ ก็ต้องทำการ compile โปรแกรมให้เป็นนามสกุล .EXE เพื่อให้มันทำงานได้ ตอนนั้น compiler ที่ผมใช้ (FORTRAN) มันมีให้เลือกว่าจะให้เรียกใช้ Math co-processor หรือไม่ ถ้าเลือกว่าให้เรียกใช้และเครื่องที่ทำการ run โปรแกรมไฟล์ .EXE นั้นมี Math co-processor โปรแกรมนั้นก็จะทำงานได้เร็ว แต่ถ้านำโปรแกรม .EXE ที่ได้นั้นไป run บนเครื่องที่ไม่มี Math co-processor มันก็จะทำงานไม่ได้ ในทางกลับกันถ้าบอกกับ compiler ว่าไม่มีการเรียกใช้ Math co-processor โปรแกรม .EXE มันจะ run บนเครื่องได้ทุกเครื่อง ไม่ว่าเครื่องนั้นจะมีหรือไม่มี Math co-processor หรือไม่ (ถึงมีมันก็ไม่เรียกใช้)
นอกจากนี้ยังมีให้เลือกว่าจะให้ไฟล์ .EXE ที่ได้นั้นเน้นไปที่ทำงานได้เร็วหรือประหยัดหน่วยความจำ ถ้าให้เน้นไปที่ทำงานได้เร็วก็จะได้ไฟล์ .EXE ขนาดใหญ่ แต่ถ้าเน้นไปที่ประหยัดหน่วยความจำก็จะได้ไฟล์ .EXE ขนาดเล็ก แถมตัวซอร์ฟแวร์เองจะตั้งค่าการคำนวณไว้ที single precision หรือนัยสำคัญ 8 ตำแหน่งเป็นค่า default เอาไว้ก่อน (เพื่อประหยัดหน่วยความจำ) ถ้าต้องการความถูกต้องมากขึ้นไปอีกก็ต้องทำการกำหนดเองว่าจะใช้ double precision หรือนัยสำคัญ 16 ตำแหน่ง ซึ่งแน่นอนว่ากินหน่วยความจำเพิ่มมากขึ้นไปอีก
  
ด้วยเหตุนี้เมื่อสัก ๒๐ กว่าปีที่แล้ว คนที่ทำงานด้าน numerical simulation นั้นจึงจำเป็นต้องรู้จักการทำงานของฮาร์ดแวร์ที่ตัวเองใช้ด้วย ไม่ใช่รู้แต่เพียงแค่วิธีใช้ซอร์ฟแวร์ ทั้งนี้เพราะเวลาเปลี่ยน platform ของเครื่อง (คือเปลี่ยนยี่ห้อผู้ผลิต mainframe) ระบบมันก็มีความแตกต่างกันอยู่ในบางจุด มันไม่เหมือนกันซะทีเดียว
  
ในยุคปัจจุบันที่หน่วยความจำมีราคาถูกลงมาก ชนิดที่เรียกว่าการคำนวณบนเครื่องไมโครคอมพิวเตอร์ตั้งความถูกต้องเอาไว้ที่นัยสำคัญ 16 ตำแหน่งเป็นหลัก และการสอนการเขียนโปรแกรมด้วยภาษาสำหรับใช้เพื่อการคำนวณโดยเฉพาะ ถูกเปลี่ยนไปเป็นการเขียนโปรแกรมด้วยภาษาสำหรับงานที่ไม่ได้มีการคำนวณมากนัก ประกอบกับการมีซอร์ฟแวร์สำเร็จรูปให้เลือกใช้กันอย่างแพร่หลาย ไม่จำเป็นต้องมานั่งเขียนโปรแกรมเองเหมือนแต่ก่อน ทำให้ปัญหาสำคัญบางเรื่องที่ผู้ใช้คอมพิวเตอร์แต่ก่อนต้องเรียนรู้และต้องระมัดระวัง กลับไม่มีการสอนในปัจจุบัน ทั้ง ๆ ที่ปัญหาดังกล่าวก็ยังคงมีอยู่
  
รูปต่าง ๆ ที่ยกมาเป็นตัวอย่างใน Memoir ฉบับนี้ ผมคำนวณด้วยเครื่อง pentium 4 2.4 GHz ติดตั้ง ram 412 MB (เครื่องที่ภาควิชาจัดสรรให้บุคลากรใช้) ใช้ระบบปฏิบัติการ Windows XP sp2 และใช้โปรแกรม spreadsheet ของ OpenOffice 3.3.0 และ Excel ของ Microsoft Office 2007 คำนวณเปรียบเทียบกัน ตัวอย่างที่ยกมาให้ดู ๓ ตัวอย่างนี้อยากให้ไปทดลองกับเครื่องของแต่ละท่านเอาเองว่ามันเป็นจริงไหม แต่ว่ามันอาจแตกต่างกันไปบ้างได้ ขึ้นอยู่กับ spec เครื่องที่ใช้ เราเริ่มเลยก็แล้วกัน

ตัวอย่างที่ ๑ การคำนวณค่า (1 + e -1) และ (1 - 1 + e)

ในทางทฤษฎีแล้วค่า (1 + e -1) และ (1 - 1 + e) จะต้องเท่ากัน และถ้าคิดเลขในใจผลก็จะออกมาเป็นเช่นนั้น แต่เมื่อทำการคิดเลขบนไมโครคอมพิวเตอร์ (ใช้โปรแกรม spreadsheet ของ OpenOffice 3.3.0) ที่ค่า e ต่าง ๆ กัน ผลก็ออกมาเป็นดังแสดงในรูปที่ ๑ จะเห็นว่าการคำนวณเมื่อเขียนคำสั่งแบบ (1 - 1 + e) นั้นให้คำตอบที่ถูกต้องเสมอ ในขณะที่การเขียนคำสั่งแบบ (1 + e -1) กลับให้คำตอบที่ผิดเพี้ยนไปจากคำตอบที่ถูกต้องเมื่อ e มีค่าเพียงแค่ .001 และหนักข้อขึ้นไปอีกเมื่อค่า e ลดต่ำลงเป็น 0.000 000 000 000 001 หรือต่ำกว่า เพราะให้คำตอบที่ผิดไปเลย (คือให้ค่าเป็นศูนย์)
อธิบายได้ไหมครับว่าเป็นเพราะอะไร :) :) :)
  
รูปที่ ๑ ผลการคำนวณค่า (1 + e -1) และ (1 - 1 + e) ที่ค่า e ต่าง ๆ กัน แม้ว่าในทางทฤษฎีแล้วทั้งสองรูปแบบจะต้องให้คำตอบเดียวกัน แต่ในทางปฏิบัติเมื่อนำมาคำนวณด้วยเครื่องคอมพิวเตอร์กลับพบว่าคำตอบแตกต่างกัน
  
ตัวอย่างที่ ๒ ผลรวมของ (1/3 + 1/3 + 1/3)

รูปที่ ๒ ข้างล่างเป็นตัวอย่างการคำนวณค่า (1/3 + 1/3 + 1/3) ด้วยวิธีที่แตกต่างกัน 3 วิธี โดยในคอลัมน์ A นั้นตัวเลขในช่อง A27, A28 และ A29 ได้มาจากการให้เครื่องคำนวณค่า 1/3 และค่าในช่อง A30 นั้นเป็นการเอาตัวเลขในช่อง A27, A28 และ A29 มาบวกรวมเข้าด้วยกัน
  
ในคอลัมน์ B นั้น ตัวเลขในช่อง B27 ได้มาจากการคัดลอกตัวเลขในคอลัมน์ A27 ด้วยการใช้คำสั่ง paste special ที่คัดลอกมาเฉพาะค่า (valve) และรูปแบบ (format) ตัวเลขในช่อง B28 และ B29 ก็ได้มาจากช่อง A28 และ A29 ด้วยวิธีการเดียวกัน ส่วนตัวเลขในช่อง B30 ก็เป็นการเอาตัวเลขในช่อง B27, B28 และ B29 มาบวกเข้าด้วยกัน
  
ในคอลัมน์ C นั้นผมใช้วิธีการป้อนค่าตัวเลขเข้าไปโดยตรง โดยเลื่อนเคอร์เซอร์ไปที่ช่อง B27 เพื่อดูว่าตัวเลขที่ปรากฏในช่อง B27 นั้นเป็นตัวเลขอะไร และก็กดคีย์บอร์ดตัวเลขนั้นลงในช่อง C27 ตัวเลขในช่อง C28 และ C29 ก็ได้มาโดยวิธีการทำนองเดียวกัน และตัวเลขในช่อง C30 ก็เป็นการเอาตัวเลขในช่อง C27, C28 และ C29 มาบวกเข้าด้วยกัน
  
แต่คราวนี้ปรากฏว่าตัวเลขในช่อง C30 นั้นแตกต่างไปจากตัวเลขในช่อง A30 และ B30 ทั้ง ๆ ที่ตัวเลขที่ปรากฏในแถวที่ 27-29 นั้นต่างก็เป็นตัวเลขเดียวกัน
  
รูปที่ ๒ การคำนวณค่าผลรวมของ (1/3 + 1/3 + 1/3) จะเห็นว่าแม้ว่าตัวเลขค่า 1/3 ที่ปรากฏในแต่ละคอลัมน์นั้นจะเป็นเลขเดียวกัน แต่เมื่อคำนวณผลรวมเข้าด้วยกันแล้วกลับได้ตัวเลขที่แตกต่างกันได้

ตัวอย่างที่ ๓ การหาค่า 1.001 ยกกำลัง 8

การคิดค่า xy นั้นมีวิธีทำอยู่ด้วยกัน ๓ วิธีคือ
  
วิธีแรก คือการคูณเข้าด้วยกันโดยตรงในรูป x*x*....*x วิธีนี้ใช้ได้ก็ต่อเมื่อ y เป็นเลขจำนวนเต็ม
  
วิธีที่สอง ถ้าซอร์ฟแวร์มีคำสั่งยกกำลัง เช่น ** ในภาษา FORTRAN หรือ ^ ในภาษา BASIC และในโปรแกรม spreadsheet ต่าง ๆ) ก็สามารถใช้คำสั่งนี้ เช่นเขียนเป็น x**y หรือ x^y
  
วิธีที่สาม ในกรณีที่ซอร์ฟแวร์ไม่มีคำสั่งยกกำลัง (ภาษาบางภาษาเป็นเช่นนั้น) จะใช้วิธี take log ก่อน จากนั้นจึงค่อยทำการ anti log กลับกล่าวคือ xy = anti log (y *lgg x)) ส่วน log นั้นจะเป็นฐาน e หรือฐาน 10 ก็ตามแต่

รูปที่ ๓ เป็นผลการคำนวณค่า 1.0018 ด้วยการใช้วิธีการทั้ง ๓ ที่กล่าวมาข้างต้น บรรทัดแรกใช้การ take log ฐาน e แล้วค่อยทำการ anti log กลับ บรรทัดที่สองก็เป็นแบบเดียวกับบรรทัดแรก แต่เปลี่ยนเป็นฐาน 10 บรรทัดที่ 3 เป็นการนำตัวเลข 1.001 จำนวน 8 ตัวมาคูณเข้าด้วยกันโดยตรง และบรรทัดที่สี่เป็นการใช้คำสั่งยกกำลัง
  
รูปที่ ๓ (บน) นั้นคำนวณด้วยการใช้โปรแกรม OpenOffice 3.3.0 ส่วนรูปที่ ๓ (ล่าง) นั้นใช้โปรแกรม Excel 2007 จะเห็นว่าโปรแกรม OpenOffice 3.3.0 นั้นไม่ว่าจะใช้วิธีการไหนจะให้คำตอบที่เหมือนกันหมด แต่ในกรณีของโปรแกรม Excel นั้นการใช้วิธี take log ก่อนแล้วค่อยทำการ anti log กลับนั้นให้ค่าที่เหมือนกับค่าที่ได้จากโปรแกรม OpenOffice 3.3.0 แต่พอใช้วิธีการคูณเข้าด้วยกันโดยตรงหรือใช้คำสั่งยกกำลัง กลับได้ค่าที่แตกต่างไป (ตัวเลขหลักสุดท้าย)


รูปที่ ๓ การคำนวณค่า (1.001)8 ด้วยวิธีที่แตกต่างกัน 4 วิธีและใช้โปรแกรมต่างกัน (คำนวณบนเครื่องเดียวกัน) โดยรูปบนเป็นผลการคำนวณโดยใช้โปรแกรม spreadsheet ของ OpenOffice 3.3.0 ส่วนรูปร่างเป็นผลการคำนวณด้วยโปรแกรม Excel ของ Mircosoft

ตัวเลขที่แสดงนั้นใช้ตัวเลข 15 หลัก คือความละเอียดสูงสุดเท่าที่เครื่องจะให้ได้ 15 หลักนี้มาจากการที่ให้เครื่องคำนวณตัวเลขสักตัวที่มันเป็นทศนิยมไม่รู้จบ (เช่น 1/3) แล้วเพิ่มจำนวนจุดทศนิยมให้มันเขียน เมื่อไรก็ตามที่ตำแหน่งสุดท้ายนั้นเป็นศูนย์ก็แสดงว่าเครื่องมันให้ความละเอียดได้มากที่สุดเท่านั้น ตัวอย่างที่ยกมานี้อาจเปลี่ยนไปตามซอร์ฟแวร์และฮาร์ดแวร์ที่ใช้

ส่วนเหตุผลที่ว่าทำไมมันจึงเป็นเช่นนั้น ก็ฝากให้เอาไปคิดกันเล่น ๆ ก่อนก็แลัวกัน เพราะบางเรื่องมันมีสาเหตุมาจากฮาร์ดแวร์ และบางเรื่องมันก็มีสาเหตุมาจากซอร์ฟแวร์

ไม่มีความคิดเห็น: