Đổi số thành chữ trong Google Sheets

Thử cách đọc ngay, rồi tạo hàm Apps Script để gõ =DOCSO(A1) trong bảng tính của bạn.

  1. 1 Chọn cách đọc
  2. 2 Sao chép mã
  3. 3 Cài vào Apps Script
  4. 4 Kiểm tra

Một triệu hai trăm năm mươi nghìn năm trăm

Đây là bản demo bằng engine của sothanhchu.com chạy ngay trong trình duyệt, không phải kết quả của Google Sheets. Hàm Apps Script bên dưới đọc cùng cách nhưng chỉ nhận số nguyên.

Bạn cần hàm cho

Số thường tạo hàm DOCSO. Số tiền tạo hàm DOCVND, ghi “đồng” ở cuối.

Cách đọc
Kiểu chữ
Đổi tên hàm (nâng cao)

Chỉ chữ cái a–z không dấu, chữ số và dấu gạch dưới; bắt đầu bằng chữ cái; không kết thúc bằng dấu gạch dưới; tối đa 30 ký tự. Không trùng hàm có sẵn hoặc địa chỉ ô như DOC1.

DOCSO()
  • Số thường
  • nghìn, linh, mốt, bốn, lăm
  • Viết hoa chữ đầu
  • nhận cả vùng ô
/**
 * Generated by sothanhchu.com - Vietnamese number to words (Google Apps Script)
 * Function: DOCSO(value) - whole numbers only
 * Config: number; nghin / linh / mot / bon / lam / ty; case: sentence
 * Pure JavaScript: no network calls, no Google services, no spreadsheet reads.
 * Input: a single cell or a range. A number below 1E+15, or text made only of digits.
 * Blank cells stay blank. In a range, a bad cell shows "#LỖI: ..."; for one cell the function throws an error message.
 */
var STC_MONEY_ = false;
var STC_OMIT_ZERO_HUNDRED_ = false;
var STC_CASE_ = 'sentence'; // sentence | lower | upper
var STC_MAX_DIGITS_ = 36;
var STC_LIMIT_ = 1e15; // cell numbers at or above this are refused (use text)
var STC_WORDS_ = {
  digits: ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín'],
  hundred: 'trăm', ten: 'mười', tens: 'mươi',
  thousand: 'nghìn', million: 'triệu', billion: 'tỷ',
  zeroTens: 'linh', one: 'mốt', four: 'bốn', five: 'lăm',
  neg: 'âm', unit: '', whole: ''
};

/**
 * Đọc số nguyên thành chữ tiếng Việt. Nhận một ô hoặc cả vùng ô.
 *
 * @param {number|string|Array<Array<number|string>>} value Một ô hoặc một vùng ô, ví dụ A1 hoặc A1:A100.
 * @return {string|Array<Array<string>>} Chữ đọc; với vùng ô thì trả về mảng cùng số dòng, số cột.
 * @customfunction
 */
function DOCSO(value) {
  return stcRun_(value);
}

function stcRun_(value) {
  if (Array.isArray(value)) {
    // A range arrives as a two-dimensional array. Convert in memory, keep the same rows and columns.
    return value.map(function (row) {
      return Array.isArray(row) ? row.map(stcCell_) : [stcCell_(row)];
    });
  }
  var r = stcConvert_(value);
  if (!r.ok) throw new Error(r.message);
  return r.text;
}

// One cell of a range: a bad cell becomes a short visible text, so the rest of the range still converts.
function stcCell_(value) {
  var r = stcConvert_(value);
  return r.ok ? r.text : '#LỖI: ' + r.short;
}

function stcFail_(short, message) {
  return { ok: false, short: short, message: message };
}

// Accepts a whole number: a numeric cell below 1E+15, or text made only of digits (optional leading minus).
function stcConvert_(value) {
  var neg = false;
  var digits;
  if (value === '' || value === null || value === undefined) return { ok: true, text: '' };
  if (typeof value === 'number') {
    if (!isFinite(value)) return stcFail_('không phải số', 'Giá trị không phải một số hữu hạn.');
    if (Math.abs(value) >= STC_LIMIT_) return stcFail_('số quá lớn', 'Ô số từ 1E+15 trở lên không giữ đủ chữ số. Hãy nhập số dưới dạng văn bản (định dạng ô là Text).');
    if (value !== Math.floor(value)) return stcFail_('số thập phân', 'Hàm chỉ đọc số nguyên; ô này có phần thập phân.');
    neg = value < 0;
    digits = String(Math.abs(value));
  } else if (typeof value === 'string') {
    var s = value.trim();
    if (s === '') return { ok: true, text: '' };
    if (s.charAt(0) === '-') {
      neg = true;
      s = s.slice(1);
    }
    if (!/^[0-9]+$/.test(s)) return stcFail_('không phải số nguyên', 'Chỉ nhận số nguyên hoặc văn bản gồm toàn chữ số (có thể có dấu trừ ở đầu).');
    digits = s;
  } else if (typeof value === 'boolean') {
    return stcFail_('đúng/sai', 'Ô chứa giá trị đúng/sai, không phải số.');
  } else {
    return stcFail_('không phải số', 'Ô chứa ngày, giờ hoặc giá trị khác, không phải số nguyên.');
  }
  digits = digits.replace(/^0+/, '');
  if (digits.length > STC_MAX_DIGITS_) return stcFail_('quá dài', 'Chỉ đọc tối đa ' + STC_MAX_DIGITS_ + ' chữ số.');
  if (digits === '') neg = false;
  var w = STC_WORDS_;
  var text = stcWords_(digits);
  if (neg) text = w.neg + ' ' + text;
  if (STC_MONEY_) {
    text += ' ' + w.unit;
    if (w.whole) text += ' ' + w.whole;
  }
  if (STC_CASE_ === 'upper') text = text.toUpperCase();
  else if (STC_CASE_ === 'sentence') text = text.charAt(0).toUpperCase() + text.slice(1);
  return { ok: true, text: text };
}

// Whole number (digits only) -> words. Groups of three digits; from 10^9 on the number is read in
// nine-digit chunks, each followed by the billion word once per chunk level (nghìn tỷ, triệu tỷ, tỷ tỷ ...).
function stcWords_(digits) {
  var w = STC_WORDS_;
  if (digits === '') return w.digits[0];
  var padded = digits;
  while (padded.length % 3 !== 0) padded = '0' + padded;
  var count = padded.length / 3;
  var vals = [];
  var top = -1;
  for (var i = 0; i < count; i++) {
    vals.push(parseInt(padded.substr(i * 3, 3), 10));
    if (top < 0 && vals[i] > 0) top = i;
  }
  var out = [];
  for (var j = 0; j < count; j++) {
    if (vals[j] === 0) continue;
    var g = count - 1 - j;
    var pos = g % 3;
    var chunk = Math.floor(g / 3);
    var piece = [stcGroup_(vals[j], j === top)];
    if (pos === 1) piece.push(w.thousand);
    if (pos === 2) piece.push(w.million);
    if (chunk > 0) {
      var lowest = true;
      for (var lower = chunk * 3; lower < g; lower++) {
        if (vals[count - 1 - lower] > 0) lowest = false;
      }
      if (lowest) {
        for (var k = 0; k < chunk; k++) piece.push(w.billion);
      }
    }
    out.push(piece.join(' '));
  }
  return out.join(' ');
}

// One group of three digits (1..999). isTop = the most significant non-zero group (no leading "không trăm").
function stcGroup_(v, isTop) {
  var w = STC_WORDS_;
  var h = Math.floor(v / 100);
  var t = Math.floor(v / 10) % 10;
  var u = v % 10;
  var s = [];
  if (h > 0) {
    s.push(w.digits[h], w.hundred);
  } else if (!isTop && !STC_OMIT_ZERO_HUNDRED_) {
    s.push(w.digits[0], w.hundred);
  }
  var zeroTensCtx = t === 0 && u > 0 && (h > 0 || !isTop);
  if (t === 0) {
    if (zeroTensCtx) s.push(w.zeroTens);
  } else if (t === 1) {
    s.push(w.ten);
  } else {
    s.push(w.digits[t], w.tens);
  }
  if (u > 0) {
    if (u === 1 && t >= 2) s.push(w.one);
    else if (u === 4 && (t >= 2 || zeroTensCtx)) s.push(w.four);
    else if (u === 5 && t >= 1) s.push(w.five);
    else s.push(w.digits[u]);
  }
  return s.join(' ');
}

Chưa chạy thử trong Google Apps Script thật. Mã mới chỉ được chạy cục bộ bằng Node.js. Sau khi dán, đối chiếu bảng “Kiểm tra sau khi cài” bên dưới. Ô số chỉ nhận số nguyên dưới 1E+15; số dài nhập dưới dạng văn bản.

Mã chỉ là JavaScript thuần: không gọi mạng, không gọi dịch vụ nào của Google. Đọc qua trước khi dán: cách tự kiểm tra.

Kiểm tra sau khi cài

Gõ từng công thức vào một ô. Kết quả phải giống cột bên phải; nếu khác, xem lỗi thường gặp.

Công thức thử và kết quả mong đợi theo cấu hình đã chọn
Gõ vào ô Google SheetsPhải ra
=DOCSO(21)Hai mươi mốt
=DOCSO(105)Một trăm linh năm
=DOCSO(1250000)Một triệu hai trăm năm mươi nghìn
=DOCSO(24)Hai mươi bốn
=DOCSO(25)Hai mươi lăm
=DOCSO(1005)Một nghìn không trăm linh năm
=DOCSO("123456789012345678")Một trăm hai mươi ba triệu bốn trăm năm mươi sáu nghìn bảy trăm tám mươi chín tỷ không trăm mười hai triệu ba trăm bốn mươi lăm nghìn sáu trăm bảy mươi támSố 18 chữ số nằm trong dấu ngoặc kép: đọc đủ mọi chữ số.
=DOCSO(1.5)#ERROR!Số thập phân không được hỗ trợ: ô báo #ERROR! kèm lời giải thích.

Thử với cả vùng ô

Nhập 3 số vào A1:A3, rồi gõ công thức vào B1. Cả ba kết quả tự điền xuống B1:B3 (B2 và B3 phải trống).

Ví dụ dùng cho cả vùng ô
ÔGiá trịCột kết quả: gõ =DOCSO(A1:A3) vào B1
A121Hai mươi mốt
A2105Một trăm linh năm
A31250000Một triệu hai trăm năm mươi nghìn

Mã được tạo ngay trên máy bạn. Lựa chọn và số bạn thử không được gửi đi và không lưu lại.

Cài vào Google Sheets

  1. Mở bảng tính trong Google Sheets.
  2. Vào menu Extensions > Apps Script (ở giao diện tiếng Việt, tên menu có thể khác). Trình soạn mã mở trong một thẻ mới.
  3. Xóa mã mẫu có sẵn trong trình soạn, rồi dán mã vừa sao chép.
  4. Bấm Save (Lưu) ở thanh trên cùng của trình soạn.
  5. Quay lại bảng tính và gõ =DOCSO(A1) vào một ô, rồi so với mục Kiểm tra sau khi cài.

Các bước trên theo hướng dẫn hàm tùy chỉnh của Google. Mã nằm trong tập lệnh gắn với bảng tính nào thì hàm chỉ dùng được trong bảng tính đó; muốn dùng ở bảng tính khác, dán lại mã (Google cũng nêu cách chia sẻ bằng sao chép tập lệnh).

Dùng cho cả vùng ô

Theo Google, khi bạn truyền một ô, hàm nhận giá trị ô; khi bạn truyền một vùng ô như A1:A100, hàm nhận một mảng hai chiều. Hàm này tận dụng điều đó: một công thức đọc cả vùng, kết quả có cùng số dòng và cột với vùng đầu vào, đúng thứ tự.

Một ô đơn lẻ vẫn dùng bình thường: =DOCSO(A1). Với một ô sai, hàm báo #ERROR! kèm lời giải thích.

Quyền truy cập và quyền riêng tư

Hàm có cần cấp quyền không?

Google nêu: hàm tùy chỉnh “never ask users to authorize access to personal data” (không bao giờ yêu cầu người dùng cấp quyền truy cập dữ liệu cá nhân), nên chỉ gọi được các dịch vụ không truy cập dữ liệu cá nhân. Mã ở đây còn không gọi dịch vụ nào của Google, không đọc bảng tính và không thêm phạm vi quyền (scope) nào. Vì vậy chúng tôi không kỳ vọng có hộp thoại cấp quyền khi dùng hàm trong ô. Vì chưa chạy trên Google thật nên chúng tôi chưa tự quan sát điều này; bạn vẫn cần đăng nhập tài khoản có quyền sửa bảng tính để mở Apps Script.

Dữ liệu của bạn đi đâu?

Số lớn và số thập phân

Số thập phân: chưa hỗ trợ, có chủ đích. Hàm chỉ đọc số nguyên. Ô có phần thập phân (kể cả ô định dạng phần trăm: Google nêu 10% thành 0,1 trong Apps Script) cho kết quả lỗi, không bao giờ làm tròn hay đọc sai. Với VND điều này khớp công cụ web, vì VND không có đơn vị nhỏ hơn đồng và chúng tôi không tự làm tròn số tiền của bạn.

Số lớn. Theo tài liệu Sheets API của Google, giá trị số của ô là kiểu double (tài liệu); số double không giữ được mọi chữ số của số rất dài. Chúng tôi chưa tìm thấy tài liệu Google nêu một giới hạn chữ số cụ thể cho ô, nên dùng ngưỡng thận trọng: ô số từ 1E+15 trở lên bị từ chối. Nếu một số dài đã bị làm tròn khi vào ô số, hàm không thể lấy lại chữ số đã mất.

Cách giữ nguyên số dài: nhập dưới dạng văn bản. Tài liệu Sheets API nêu rằng khi bạn gõ dấu nháy đơn ' trước số (ví dụ '123456789012345678), giá trị được lưu là chuỗi và dấu nháy không nằm trong giá trị. Hàm đọc chuỗi gồm toàn chữ số tới 36 chữ số.

Lỗi thường gặp

Dấu hiệuNguyên nhân thường gặpCách xử lý
Ô báo không nhận ra hàm (#NAME?)Chưa bấm Lưu trong Apps Script; gõ sai tên so với tên hàm trong khung mã; tên hàm trùng một hàm có sẵn; hoặc tên kết thúc bằng dấu gạch dưới.Lưu tập lệnh, gõ đúng tên như trong khung mã. Google nêu tên hàm phải khác tên hàm có sẵn như SUM, không được kết thúc bằng gạch dưới, và phải khai báo dạng function tên() (mã ở đây đã làm đúng).
#ERROR! kèm “Exceeded maximum execution time”Một lần gọi hàm chạy quá 30 giây (giới hạn của Google).Chia vùng ô nhỏ hơn. Giới hạn này do Google đặt và có thể thay đổi.
#ERROR! kèm lời giải thích của hàmÔ đơn có phần thập phân, số từ 1E+15 trở lên, văn bản không phải số nguyên, ngày/giờ hoặc đúng/sai.Đọc lời giải thích (rê chuột vào ô). Sửa giá trị hoặc nhập số dưới dạng văn bản.
Ô trong vùng hiện #LỖI: …Chính ô đó không hợp lệ; các ô khác vẫn được đọc.Xem lý do ngắn sau dấu hai chấm và sửa ô đó.
Kết quả mảng báo lỗi, không tràn xuốngCác ô bên dưới (hoặc bên cạnh) không trống nên kết quả không thể tràn ra.Xóa các ô đó hoặc đặt công thức ở chỗ có đủ ô trống.
“Script invoked too many times per second”Hàm bị gọi quá dày, thường do dán công thức một ô cho hàng nghìn dòng.Dùng một công thức cho cả vùng, ví dụ =DOCSO(A1:A1000) (theo khuyến nghị của Google).
“You do not have permission to call …”Chỉ xảy ra nếu bạn tự thêm vào mã lệnh gọi một dịch vụ cần cấp quyền. Mã tạo ở đây không gọi dịch vụ nào.Dùng mã gốc, hoặc không dùng dịch vụ đó trong hàm tùy chỉnh (Google không cho).
Ô ngày hoặc phần trăm cho #LỖI / #ERROR!Google nêu ngày giờ thành đối tượng Date và phần trăm thành số thập phân trong Apps Script.Chuyển ô về số nguyên thường trước khi đọc.
Chữ khác ví dụ (nghìn/ngàn, linh/lẻ…)Cấu hình khác với mã đang dán.Bảng “Kiểm tra sau khi cài” tính theo cấu hình hiện tại. Tạo lại mã nếu bạn đổi lựa chọn.

Mã hoạt động thế nào

Mọi cấu hình dùng cùng một lõi mã đã được kiểm tra. Lựa chọn của bạn chỉ đổi bảng từ (nghìn hay ngàn, linh hay lẻ…), ba cờ Đúng/Sai và kiểu chữ ở đầu tập lệnh. Quy tắc đọc là quy tắc của engine đang chạy ở trang chủ: cùng cách chia nhóm ba chữ số, cùng xử lý “linh/lẻ”, “mốt”, “tư”, “lăm” và “nghìn tỷ”.

Mã được kiểm tra thế nào, và chưa kiểm tra gì

An toàn và minh bạch

Mã hiển thị đầy đủ, không mã hóa, không rút gọn. Nó không có: UrlFetchApp, fetch, XMLHttpRequest, eval, new Function, nhập mã từ ngoài, mã đã mã hóa, hay mã theo dõi. Tự kiểm tra trong 30 giây: trong trình soạn Apps Script nhấn Ctrl+F và tìm UrlFetchApp, fetch, http, eval, SpreadsheetApp. Mã đúng sẽ không tìm thấy từ nào trong số đó ngoài các dòng ghi chú ở đầu.

Nút “Tải xuống mã nguồn (.gs.txt)” chỉ tạo một tệp văn bản ngay trên máy bạn, chứa đúng đoạn mã trên. sothanhchu.com không phát hành tập tin cài đặt hay add-in.

Hàm Apps Script hay công cụ trên web?

Không cách nào hợp với mọi trường hợp.

Cách làmCài đặtTự cập nhật khi số đổiHợp khi
Hàm Apps Script (trang này)Dán mã vào Apps Script của bảng tính, một lần cho mỗi bảng tínhCó, nếu truyền ô hoặc vùng ô trực tiếpCần chữ nằm ngay trong bảng tính và đổi theo số.
Công cụ trên web của sothanhchu.comKhông cài gìKhông: kết quả là chữ cố định, dán vàoĐổi nhanh một lần, hoặc dùng đổi từng số.
ExcelKhác hẳn: macro, add-in hoặc công thứcTùy cáchBạn dùng Excel thay vì Google Sheets: xem trang Excel hoặc code VBA.

Câu hỏi thường gặp

Có đặt tên hàm là VND trong Google Sheets được không?

Được, miễn tên không trùng hàm có sẵn của Google Sheets và không kết thúc bằng dấu gạch dưới: trình tạo kiểm tra điều này khi bạn đổi tên. Mặc định là DOCSO/DOCVND vì trong Excel tên VND đã được nhiều add-in và đoạn mã dùng với cú pháp khác nhau, nên công thức chép qua lại giữa Excel và Sheets dễ cho kết quả khác nhau. DOCSO vẫn là tên khá phổ biến: nếu bảng tính của bạn đã có hàm cùng tên, hãy đổi.

Có đọc được số thập phân không?

Chưa. Hàm báo lỗi thay vì đọc sai. Để đọc số thập phân, dùng công cụ trên trang chủ.

Có phải trả tiền hoặc đăng ký không?

Trang và mã miễn phí, không cần tài khoản ở sothanhchu.com. Bạn cần tài khoản Google có quyền sửa bảng tính để mở Apps Script.

Đã chạy thử trên Google Sheets thật chưa?

Chưa. Chúng tôi chưa có môi trường Google Apps Script để chạy mã, nên đây là mã đã được kiểm tra bằng cách chạy trên máy cục bộ, không phải bằng Google. Hãy dùng bảng “Kiểm tra sau khi cài” để tự xác nhận trong bảng tính của bạn.

Dùng trên điện thoại được không?

Chúng tôi chưa kiểm tra việc cài và dùng tập lệnh trên ứng dụng Google Sheets cho điện thoại. Hãy cài mã trên máy tính, rồi mở bảng tính trên điện thoại để xem kết quả.

Công cụ liên quan