Đổ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 Chọn cách đọc
- 2 Sao chép mã
- 3 Cài vào Apps Script
- 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.
Đổ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.
- Số thường
- nghìn, linh, mốt, bốn, lăm
- Viết hoa chữ đầu
- nhận cả vùng ô
Tên hàm chưa hợp lệ nên chưa tạo mã. Sửa tên ở mục “Đổi tên hàm” hoặc dùng tên mặc định.
/**
* 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.
| Gõ vào ô Google Sheets | Phả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).
| Ô | Giá trị | Cột kết quả: gõ =DOCSO(A1:A3) vào B1 |
|---|---|---|
| A1 | 21 | Hai mươi mốt |
| A2 | 105 | Một trăm linh năm |
| A3 | 1250000 | Mộ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
- Mở bảng tính trong Google Sheets.
- 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.
- Xóa mã mẫu có sẵn trong trình soạn, rồi dán mã vừa sao chép.
- Bấm Save (Lưu) ở thanh trên cùng của trình soạn.
- 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ự.
- Ô trống giữ nguyên là ô trống, đúng vị trí.
- Một ô sai không làm hỏng cả vùng. Ô đó hiện
#LỖI: …kèm lý do ngắn (ví dụ “số thập phân”), các ô còn lại vẫn có chữ. - Phải còn chỗ trống bên dưới. Google nêu: kết quả mảng tràn sang các ô kề bên nếu chúng đang trống; nếu sẽ đè lên nội dung có sẵn, hàm báo lỗi thay vì ghi đè.
- Truyền thẳng vùng ô vào hàm để kết quả tự cập nhật khi số đổi. Google nêu: hàm chỉ tính lại khi ô hoặc vùng được tham chiếu trực tiếp là đối số.
- Hiệu năng: hàm xử lý cả mảng trong bộ nhớ, không đọc bảng tính từng ô. Google cũng khuyên viết hàm chạy một lần cho cả vùng thay vì gọi nhiều lần, vì gọi quá dày có thể gây lỗi “Script invoked too many times per second” (giới hạn của Google). Mỗi lần gọi hàm phải xong trong 30 giây.
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?
- Mã của chúng tôi không gửi giá trị ô tới sothanhchu.com hay bất kỳ nơi nào, và không có lệnh mạng nào. Đây là điều bạn đọc và kiểm tra được trong mã.
- Google Sheets và Apps Script vẫn xử lý bảng tính của bạn như mọi lúc bạn dùng chúng. Chúng tôi không nói rằng Google không xử lý dữ liệu; chỉ nói rằng mã này không gửi thêm gì ra ngoài.
- Trang này tạo mã và chạy bản demo ngay trong trình duyệt; số bạn gõ vào ô “Thử trước khi cài” không được gửi đi.
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ệu | Nguyên nhân thường gặp | Cá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ống | Cá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ỷ”.
- Hàm đọc số nguyên: ô số nhỏ hơn 1E+15, hoặc văn bản toàn chữ số (có thể có dấu trừ), tối đa 36 chữ số.
- Ô trống cho ra ô trống. Ngày, giờ, đúng/sai và văn bản khác cho lỗi dễ hiểu, không bao giờ cho chữ số bị đọc nhầm.
- Các hàm phụ có tên kết thúc bằng dấu gạch dưới. Theo Google đó là hàm riêng tư nên không xuất hiện như hàm dùng được trong ô.
Mã được kiểm tra thế nào, và chưa kiểm tra gì
- Đã làm: chạy chính đoạn mã này trên máy chủ của chúng tôi bằng Node.js (không phải Google), 28 cấu hình, 7.644 phép so sánh với engine của trang chủ và với một bộ đọc số độc lập, 7.644 kết quả trùng khớp; cả ô đơn lẫn vùng ô. Có thử cố tình làm hỏng mã để chắc rằng phép so sánh phát hiện được lỗi.
- Chưa làm: chạy trong Google Apps Script thật. Chạy bằng Node.js chỉ chứng minh logic JavaScript, không chứng minh cách Google Sheets truyền ô, ngày và số vào hàm. Vì vậy bảng “Kiểm tra sau khi cài” là bước xác nhận trong bảng tính của bạn.
- Chưa làm: thử trên ứng dụng điện thoại và trên mọi cài đặt ngôn ngữ/vùng của Google Sheets.
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àm | Cài đặt | Tự cập nhật khi số đổi | Hợ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ính | Có, nếu truyền ô hoặc vùng ô trực tiếp | Cần chữ nằm ngay trong bảng tính và đổi theo số. |
| Công cụ trên web của sothanhchu.com | Khô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ố. |
| Excel | Khác hẳn: macro, add-in hoặc công thức | Tùy cách | Bạ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
- Đổi số thành chữ: nhập từng số và xem giải thích vì sao đọc như vậy.
- Đổi số thành chữ trong Excel: dán cả cột, không cần macro.
- Đổi số tiền thành chữ: VND, USD, EUR, JPY với phần lẻ và làm tròn có kiểm soát.