Monday, March 15, 2010

Giải thuật đổi từ số sang chữ tiếng Việt

Trong một số trường hợp, bạn có một con số (ví dụ: 5787) và cần chuyển nó sang dạng chữ (năm nghìn bảy trăm tám mươi bảy)… Đó là một cách rất thường dùng trong các giấy tờ kế toán, cũng như nhiều trường hợp muốn tránh nhầm lẫn từ người dùng khi sử dụng để đọc số tiền hay một cái gì đó tương tự.
Bài viết này, tôi xin giới thiệu một giải thuật đơn giản làm được việc này, và trong nỗ lực của mình, tôi cũng xin đưa ra một số đoạn code ví dụ để các nhà phát triển sử dụng trong trường hợp cần thiết (xem thêm quy định về giấy phép ở cuối bài này).
Thật ra, nguyên tắc đọc số trong tiếng Việt cũng tương đối phức tạp vì nó có quá nhiều trường hợp “ngoại lệ”, ví dụ như có lúc sử dụng “mươi” rồi “lẻ”… Tuy nhiên, không phải là không thể phân tích ra được (Thực tế là có khá nhiều người xài rồi và được ứng dụng rộng rãi trong các phần mềm xử lý giao dịch).


Các bạn cũng có thể xem demo rõ hơn tại: http://kakalia.co.cc/demo/docso.html

Giải thuật tổng quát

Chúng ta có thể nhận thấy rằng các chữ số thường được chia ra thành nhiều block (tỷ, triệu, nghìn, đơn vị), và người ta sẽ thêm một hậu tố ở sau mỗi block. Mỗi block đó có cách đọc hoàn toàn giống nhau (trăm, chục, đơn vị). Như vậy, cách đọc số là tách số ra thành nhiều block, rồi gọi hàm đọc từng block sau đó thêm hậu tố vào là xong.
Trước hết, khởi tạo mảng các chữ số để sử dụng nhiều lần:
mangso = [ 'không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín' ]
Các hàm bổ trợ của chương trình như sau: (viết bằng mã pseudo-code)
FUNCTION dochangtrieu( so : Integer, daydu : Boolean )
    trieu = so DIV 1.000.000
    so = so MOD 1.000.000
    IF trieu > 0 THEN
        chuoi = docblock( trieu, daydu ) + " trieu"
        daydu = TRUE
    ENDIF
    nghin = so DIV 1.000
    so = so MOD 1.000
    IF nghin > 0 THEN
        chuoi += docblock( nghin, daydu ) + " nghìn"
        daydu = TRUE
    ENDIF
    IF so > 0 THEN
        chuoi += docblock( so, daydu )
    ENDIF
    RETURN chuoi
END.
FUNCTION docblock( so : Integer, daydu : Boolean )
    tram = so DIV 100
    so = so MOD 100
    IF daydu OR  tram > 0 THEN
        chuoi = " " + mangso[ tram ] + " trăm"
        chuoi += dochangchuc(so, TRUE)
    ELSE
        chuoi = dochangchuc(so, FALSE)
    RETURN chuoi
END.
FUNCTION dochangchuc(so : Integer, daydu : Boolean)
    chuc = so DIV 10
    donvi = so MOD 10
    IF chuc > 1 THEN
        chuoi = " " + mangso[ chuc ] + " mươi"
        IF donvi = 1 THEN
            chuoi += " mốt"
        ENDIF
    ELSE IF chuc = 1 THEN
        chuoi = " mười"
        IF donvi = 1 THEN
            chuoi += " một"
        ENDIF
    ELSE IF daydu AND donvi > 0 THEN
        chuoi = " lẻ"
    ENDIF
    IF donvi = 5 AND chuc > 1 THEN
        chuoi += " lăm"
    ELSE IF donvi > 1 OR (donvi = 1 AND chuc = 0) THEN
        chuoi += " " + mangso[ donvi ]
    ENDIF
    RETURN chuoi
END.
Bây giờ, ta sẽ viết hàm đọc số chính:
FUNCTION docso( so : Integer )
    IF so = 0 THEN
        RETURN mangso[ 0 ];
    ENDIF
    REPEAT
        ty = so MOD 1.000.000.000
        so = so DIV 1.000.000.000
        IF so > 0 THEN
            chuoi = dochangtrieu( ty, TRUE ) + hauto + chuoi
        ELSE
            chuoi = dochangtrieu( ty, FALSE) + hauto + chuoi
        ENDIF
        hauto = " tỷ"
    UNTIL so <= 0
    RETURN chuoi
END.
OK, với đoạn mã giả này thì bạn có thể viết lại bằng bất cứ ngôn ngữ nào. Dưới đây, tôi sẽ viết vài loại ngôn ngữ thông dụng mà tôi biết, nếu bạn có thể viết trong ngôn ngữ khác thì hãy gửi lại cho tôi để tôi chia sẻ cho mọi người.

Mã Javascript

Đoạn mã bên dưới chạy trên tất cả các trình duyệt.
var mangso = ['không','một','hai','ba','bốn','năm','sáu','bảy','tám','chín'];
function dochangchuc(so,daydu)
{
 var chuoi = "";
 chuc = Math.floor(so/10);
 donvi = so%10;
 if (chuc>1) {
  chuoi = " " + mangso[chuc] + " mươi";
  if (donvi==1) {
   chuoi += " mốt";
  }
 } else if (chuc==1) {
  chuoi = " mười";
  if (donvi==1) {
   chuoi += " một";
  }
 } else if (daydu && donvi>0) {
  chuoi = " lẻ";
 }
 if (donvi==5 && chuc>1) {
  chuoi += " lăm";
 } else if (donvi>1||(donvi==1&&chuc==0)) {
  chuoi += " " + mangso[ donvi ];
 }
 return chuoi;
}
function docblock(so,daydu)
{
 var chuoi = "";
 tram = Math.floor(so/100);
 so = so%100;
 if (daydu || tram>0) {
  chuoi = " " + mangso[tram] + " trăm";
  chuoi += dochangchuc(so,true);
 } else {
  chuoi = dochangchuc(so,false);
 }
 return chuoi;
}
function dochangtrieu(so,daydu)
{
 var chuoi = "";
 trieu = Math.floor(so/1000000);
 so = so%1000000;
 if (trieu>0) {
  chuoi = docblock(trieu,daydu) + " triệu";
  daydu = true;
 }
 nghin = Math.floor(so/1000);
 so = so%1000;
 if (nghin>0) {
  chuoi += docblock(nghin,daydu) + " nghìn";
  daydu = true;
 }
 if (so>0) {
  chuoi += docblock(so,daydu);
 }
 return chuoi;
}
function docso(so)
{
        if (so==0) return mangso[0];
 var chuoi = "", hauto = "";
 do {
  ty = so%1000000000;
  so = Math.floor(so/1000000000);
  if (so>0) {
   chuoi = dochangtrieu(ty,true) + hauto + chuoi;
  } else {
   chuoi = dochangtrieu(ty,false) + hauto + chuoi;
  }
  hauto = " tỷ";
 } while (so>0);
 return chuoi;
}

Mã PHP

Đoạn mã bên dưới chỉ chạy với PHP 4 trở lên. Nhưng đừng lo lắng vì chuyện đó, vì hầu hết các máy chủ PHP đều hỗ trợ chuẩn PHP5 mà, chạy rất tốt.
$mangso = array('không','một','hai','ba','bốn','năm','sáu','bảy','tám','chín');
function dochangchuc($so,$daydu)
{
 global $mangso;
 $chuoi = "";
 $chuc = floor($so/10);
 $donvi = $so%10;
 if ($chuc>1) {
  $chuoi = " " . $mangso[$chuc] . " mươi";
  if ($donvi==1) {
   $chuoi .= " mốt";
  }
 } else if ($chuc==1) {
  $chuoi = " mười";
  if ($donvi==1) {
   $chuoi .= " một";
  }
 } else if ($daydu && $donvi>0) {
  $chuoi = " lẻ";
 }
 if ($donvi==5 && $chuc>1) {
  $chuoi .= " lăm";
 } else if ($donvi>1||($donvi==1&&chuc==0)) {
  $chuoi .= " " . $mangso[$donvi];
 }
 return $chuoi;
}
function docblock($so,$daydu)
{
 global $mangso;
 $chuoi = "";
 $tram = floor($so/100);
 $so = $so%100;
 if ($daydu || $tram>0) {
  $chuoi = " " . $mangso[$tram] . " trăm";
  $chuoi .= dochangchuc($so,true);
 } else {
  $chuoi = dochangchuc($so,false);
 }
 return $chuoi;
}
function dochangtrieu($so,$daydu)
{
 $chuoi = "";
 $trieu = floor($so/1000000);
 $so = $so%1000000;
 if ($trieu>0) {
  $chuoi = docblock($trieu,$daydu) . " triệu";
  $daydu = true;
 }
 $nghin = floor($so/1000);
 $so = $so%1000;
 if ($nghin>0) {
  $chuoi .= docblock($nghin,$daydu) . " nghìn";
  $daydu = true;
 }
 if ($so>0) {
  $chuoi .= docblock($so,$daydu);
 }
 return $chuoi;
}
function docso($so)
{
        global $mangso;
        if ($so==0) return $mangso[0];
 $chuoi = "";
 $hauto = "";
 do {
  $ty = $so%1000000000;
  $so = floor($so/1000000000);
  if ($so>0) {
   $chuoi = dochangtrieu($ty,true) . $hauto . $chuoi;
  } else {
   $chuoi = dochangtrieu($ty,false) . $hauto . $chuoi;
  }
  $hauto = " tỷ";
 } while ($so>0);
 return $chuoi;
}

Mã C/C++

Giờ là một đoạn code chạy trên C/C++:
string mangso[10] = {'không','một','hai','ba','bốn','năm','sáu','bảy','tám','chín'};
string dochangchuc(int so, bool daydu)
{
 string chuoi = "";
 int chuc = so/10;
 int donvi = so%10;
 if (chuc>1) {
  chuoi = " " + mangso[chuc] + " mươi";
  if (donvi==1) {
   chuoi += " mốt";
  }
 } else if (chuc==1) {
  chuoi = " mười";
  if (donvi==1) {
   chuoi += " một";
  }
 } else if (daydu && donvi>0) {
  chuoi = " lẻ";
 }
 if (donvi==5 && chuc>1) {
  chuoi += " lăm";
 } else if (donvi>1||(donvi=1&&chuc==0)) {
  chuoi += " " + mangso[donvi];
 }
 return chuoi;
}
string docblock(int so, bool daydu)
{
 string chuoi = "";
 int tram = so/100;
 so = so%100;
 if (daydu || tram>0) {
  chuoi = " " + mangso[tram] + " trăm";
  chuoi += dochangchuc(so,true);
 } else {
  chuoi = dochangchuc(so,false);
 }
 return chuoi;
}
string dochangtrieu(int so, bool daydu)
{
 string chuoi = "";
 int trieu = so/1000000;
 so = so%1000000;
 if (trieu>0) {
  chuoi = docblock(trieu,daydu) + " triệu";
  daydu = true;
 }
 int nghin = so/1000;
 so = so%1000;
 if (nghin>0) {
  chuoi += docblock(nghin,daydu) + " nghìn";
  daydu = true;
 }
 if (so>0) {
  chuoi += docblock(so,daydu);
 }
 return chuoi;
}
string docso(int so)
{
        if (so==0) { return mangso[0]; }
 string chuoi = "";
 string hauto = "";
 int ty=0;
 do {
  ty = so%1000000000;
  so = so/1000000000;
  if (so>0) {
   chuoi = dochangtrieu(ty,true) + hauto + chuoi;
  } else {
   chuoi = dochangtrieu(ty,false) + hauto + chuoi;
  }
  hauto = " tỷ";
 } while (so>0);
 return chuoi;
}
Các bạn cũng có thể xem demo rõ hơn tại: http://kakalia.co.cc/demo/docso.html
—————
Tất cả các đoạn mã (mã chạy được và mã giả) trong bài viết này được đặt dưới giấy phép Creative Commons – Attribution 3.0 Unported, phần còn lại (không phải mã nguồn) do tác giả giữ bản quyền.

5 comments:

  1. Bài viết rất hữu ích. Cảm ơn bạn đã chia sẻ.

    ReplyDelete
  2. Hàm này của bạn không đọc số thực à

    ReplyDelete
  3. thêm mấy dòng mã nữa để đọc sau dấu phảy nhé
    function docso(so){
    if (so==0) return mangso[0];
    var chuoi = "", hauto = "", tiento = ' ', phancach = ' ';
    var list = [];
    if((so+'').split('.').length>1){
    phancach = ' phảy';
    return convert_number_to_words((so+'').split('.')[0]) + phancach + convert_number_to_words((so+'').split('.')[1]);
    }else if((so+'').split('-').length>1){
    tiento = 'Âm';
    return tiento + convert_number_to_words((so+'').split('-')[1]);
    }else{
    do {
    ty = so%1000000000;
    so = Math.floor(so/1000000000);
    if (so>0) {
    chuoi = dochangtrieu(ty,true) + hauto + chuoi;
    } else {
    chuoi = dochangtrieu(ty,false) + hauto + chuoi;
    }
    hauto = " tỷ";
    } while (so>0);
    return chuoi;
    }
    return chuoi;
    }

    ReplyDelete
  4. vẫn còn lối bạn ơi:
    361111 (bằng chữ: ba trăm sáu mươi mốt một nghìn một trăm
    mười một một đồng)

    ReplyDelete

Biểu mẫu liên hệ

Name

Email *

Message *