Four Thai baht-text libraries tested on 3,289 amounts against LibreOffice
We compared the latest thai-baht-text, bahttext (npm), PyThaiNLP and bahttext (PyPI) with LibreOffice's BAHTTEXT. Ordinary amounts read almost identically, but every library has cases to watch: zero, negatives, one satang and three decimal places.
Thai receipts, tax invoices and cheques spell the amount out in words: 1,234 baht becomes “หนึ่งพันสองร้อยสามสิบสี่บาทถ้วน”. Most Thai developers install a library for this rather than writing it. While building this site’s baht-text converter, we wanted to know how closely the popular libraries agree, so we tested the latest versions of four of them on 3,289 amounts.
The overall result was better than we expected: ordinary amounts read correctly almost everywhere. But every library has specific cases to watch, and they would reach customers’ documents unnoticed.
How we tested
The reference is BAHTTEXT in LibreOffice Calc 24.2, LibreOffice’s version of the Excel function (in OpenDocument it is named COM.MICROSOFT.BAHTTEXT). We had no Excel to compare with, so we have not confirmed that Excel agrees on every amount. We ran LibreOffice headless and saved its reading of every amount to one file.
The 3,289 amounts, counted by value, are 1,269 whole numbers, 665 with one decimal, 1,055 with two and 300 with three, including special cases such as 0, 1, 11, 21, 101 and 1,000,001, and amounts in the trillions. There are no negatives in the set; we tried those separately.
The libraries (latest versions on 24 September 2026)
| Library | Language |
|---|---|
| thai-baht-text 2.0.5 (npm) | JavaScript |
| bahttext 2.4.0 (npm, jojoee) | JavaScript |
PyThaiNLP 5.3.7 (pythainlp.util.bahttext) | Python |
| bahttext 1.0.2 (PyPI) | Python |
LibreOffice’s output for all 3,289 amounts is available to download (two-column TSV: amount, reading). Use it as a test set for any library.
Two things that are not bugs
Before counting anything as wrong, the differences that are only a matter of style have to be set aside.
เอ็ด or หนึ่ง for a final 1. The Royal Institute says that under Thai rules a final ๑ in a number of two or more digits is read “เอ็ด” only: ๑๐๑ is “หนึ่งร้อยเอ็ด” and ๕๐๑,๗๔๑,๒๒๑ is “ห้าร้อยเอ็ดล้าน…”. It suggests the “หนึ่ง” reading still heard comes from naval radio practice, where it avoids mishearing over waves and engines (same rule, another page). LibreOffice, however, uses “เอ็ด” only when the tens digit is not zero, so it reads 101 as “หนึ่งร้อยหนึ่ง”. We use LibreOffice as the yardstick because it is what people actually produce documents with, not because it is more correct. A library that says “หนึ่งร้อยเอ็ด” follows the Royal Institute, so we do not count this as a bug.
A leading “ศูนย์บาท”. PyThaiNLP and PyPI bahttext read 0.9 as “ศูนย์บาทเก้าสิบสตางค์” (“zero baht ninety satang”); LibreOffice says “เก้าสิบสตางค์”. That is formatting, not an error.
Result: ordinary amounts agree
On the 2,989 amounts with at most two decimals, setting aside those two points, all four libraries match LibreOffice almost everywhere. The exceptions are thai-baht-text on the two zero rows (0 and 0.0), PyPI bahttext on all 15 amounts ending in .01, and npm bahttext on 11 amounts whose millions end in 01, such as 701,314,828.05.
Everything we found, including the negatives we tried separately:
| Library | Finding | Example |
|---|---|---|
| thai-baht-text 2.0.5 | 0 gives an empty string | 0 → "" |
| thai-baht-text 2.0.5 | Negatives are not supported: “undefined” appears and the digits shift; values between -1 and 0 come out positive | -5 → "undefinedสิบห้าบาทถ้วน", -100 → "undefinedพันหนึ่งร้อยบาทถ้วน", -0.5 → "ห้าสิบสตางค์" |
| bahttext 1.0.2 (PyPI) | One satang reads “เอ็ดสตางค์”, though 1 satang is a single digit, which the เอ็ด rule does not cover | 671.01 → “หกร้อยเจ็ดสิบเอ็ดบาทเอ็ดสตางค์” |
| PyThaiNLP 5.3.7 | Drops the minus sign for values between -1 and 0 | -0.5 → “ศูนย์บาทห้าสิบสตางค์” (while -1.5 is correctly “ลบหนึ่งบาทห้าสิบสตางค์”) |
| bahttext 2.4.0 (npm) | Applies the เอ็ด rule inconsistently (each reading is valid under one convention, but it mixes them within a number) | 101 → “หนึ่งร้อยหนึ่ง” but 101000000 → “หนึ่งร้อยเอ็ดล้าน” |
On the last row: npm bahttext’s README says it is tested against Google Sheets’ BAHTTEXT, so we tried it there: =BAHTTEXT(101000000) gives “หนึ่งร้อยหนึ่งล้านบาทถ้วน” (checked 24 September 2026), the same as LibreOffice. The library therefore differs from its own reference, even though “หนึ่งร้อยเอ็ดล้าน” is the reading the Royal Institute prescribes (its own example is “ห้าร้อยเอ็ดล้าน”); the library’s non-standard reading is 101 as “หนึ่งร้อยหนึ่ง”. The problem is inconsistency, not wrong Thai.
On negatives: thai-baht-text’s README does not claim to support them; npm bahttext’s does, and it read every negative we tried correctly. If your system can never pass 0 or a negative, these rows do not affect you. But credit notes with negative totals and zero-value lines do happen.
Three decimals: two languages, two methods
With three decimal places, the libraries form two pairs, and each pair agrees with itself on every amount:
| Library | What it does with the third decimal | Real differences from LibreOffice (of 300) |
|---|---|---|
| thai-baht-text 2.0.5 | truncates | 300 |
| bahttext 2.4.0 (npm) | truncates | 300 |
| PyThaiNLP 5.3.7 | rounds a binary float | 156 |
| bahttext 1.0.2 (PyPI) | rounds a binary float | 156 |
LibreOffice rounds half up. All 300 three-decimal amounts in the test set end in 5, exactly halfway, which is where the methods disagree most; when the last digit is 1 to 4, truncating and rounding give the same result.
For 3,282.995, LibreOffice rounds to “สามพันสองร้อยแปดสิบสามบาทถ้วน” (3,283 baht), while the JavaScript libraries truncate to “สามพันสองร้อยแปดสิบสองบาทเก้าสิบเก้าสตางค์” (3,282 baht 99 satang). For 2,454.495, LibreOffice gives fifty satang and PyThaiNLP forty-nine, because in binary the value is stored as 2,454.49499….
Both JavaScript libraries accept strings, but they still truncate. So the fix is the same for all four: round to two decimals in decimal arithmetic before calling. In Python:
from decimal import Decimal, ROUND_HALF_UP
from pythainlp.util import bahttext
amount = Decimal("2454.495").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
print(bahttext(float(amount))) # สองพันสี่ร้อยห้าสิบสี่บาทห้าสิบสตางค์
If amounts come from a two-place DECIMAL column, this rarely arises.
Try it yourself
JavaScript:
const thaiBahtText = require('thai-baht-text');
const { bahttext } = require('bahttext');
console.log(JSON.stringify(thaiBahtText(0))); // ""
console.log(thaiBahtText(-5)); // undefinedสิบห้าบาทถ้วน
console.log(bahttext(101)); // หนึ่งร้อยหนึ่งบาทถ้วน
console.log(bahttext(101000000)); // หนึ่งร้อยเอ็ดล้านบาทถ้วน
Python:
from bahttext import bahttext
from pythainlp.util import bahttext as pythainlp_bahttext
print(bahttext(671.01)) # หกร้อยเจ็ดสิบเอ็ดบาทเอ็ดสตางค์
print(pythainlp_bahttext(-0.5)) # ศูนย์บาทห้าสิบสตางค์
Recommendations
- Decide between เอ็ด and หนึ่ง first. To follow the Royal Institute, pick a library that says “หนึ่งร้อยเอ็ด”, and pin it with a test.
- Round to two decimals in decimal arithmetic before calling; do not pass long decimals in.
- Test 0, negatives (including values between -1 and 0) and .01 before using a library on real documents; those are where we found problems.
- For a test set, use the 3,289 LibreOffice readings.
All four libraries handle most work well; we are not saying any of them should be avoided, only that you should know how yours treats these cases. This site’s tool runs in the browser, works on the digits as typed rather than through binary floats, rounds satang half up, and offers both เอ็ด and หนึ่ง: baht-text converter.
Tested on 24 September 2026 with the versions above; newer versions may have fixed these. The data file is released under CC BY 4.0, like our articles.