Skip to content
KoishiAI
ไทย
← Back to all articles
thai-nlp python javascript tools how-this-site-is-built

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.

AI-drafted from cited sources, fact-checked against them by an automated pipeline, and read by a human editor for clarity. How we work · Standards · Report an error

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)

LibraryLanguage
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:

LibraryFindingExample
thai-baht-text 2.0.50 gives an empty string0 → ""
thai-baht-text 2.0.5Negatives 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 cover671.01 → “หกร้อยเจ็ดสิบเอ็ดบาทเอ็ดสตางค์”
PyThaiNLP 5.3.7Drops 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:

LibraryWhat it does with the third decimalReal differences from LibreOffice (of 300)
thai-baht-text 2.0.5truncates300
bahttext 2.4.0 (npm)truncates300
PyThaiNLP 5.3.7rounds a binary float156
bahttext 1.0.2 (PyPI)rounds a binary float156

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.

Frequently asked questions

Which Thai baht-text library can I use? ▼
For non-negative amounts with at most two decimals, all four we tested match LibreOffice almost everywhere, apart from the choice between เอ็ด and หนึ่ง. The real errors: thai-baht-text 2.0.5 returns an empty string for 0 and does not support negatives, PyPI bahttext 1.0.2 reads one satang as เอ็ดสตางค์, and PyThaiNLP 5.3.7 drops the minus sign for values between -1 and 0. npm bahttext 2.4.0 reads 101 million as หนึ่งร้อยเอ็ดล้าน, which follows the Royal Institute rule but not Google Sheets, its stated reference, nor its own reading of 101 as หนึ่งร้อยหนึ่ง. Test those cases before relying on one.
หนึ่งร้อยเอ็ด or หนึ่งร้อยหนึ่ง — which is right? ▼
The Royal Institute (ราชบัณฑิตยสภา) says that under Thai rules a final ๑ in a number of two or more digits is read เอ็ด only: ๑๐๑ is หนึ่งร้อยเอ็ด and 501 million is ห้าร้อยเอ็ดล้าน. It attributes the หนึ่ง reading to naval radio practice. LibreOffice and Google Sheets, however, read 101 million as หนึ่งร้อยหนึ่งล้าน. To match documents from those programs, pick the same style and use it consistently.
Why does 2.675 come out as 67 satang? ▼
Both JavaScript libraries cut off everything after the second decimal, even when given a string. The Python libraries take a binary floating-point number, in which 2.675 is 2.67499…, so it rounds to 67. To round half up by the digits as typed, round to two decimals with decimal arithmetic, such as Python's Decimal, before calling the library.