On this page
What it does#
In Arabic, the form of a number depends on what it counts. «ثلاثة كتب» (three books) and «ثلاث سيارات» (three cars) use different forms of three. A number also changes with its grammatical case, and it has its own rules for 11 to 19, for 200 and for 2000. NumberToWords::convert($n, 'ar', $options) handles these. Without options, it gives the plain counting form, the same text as in earlier releases.
The options are an RtlyKit\Number\ArabicOptions object, or an array with the same keys. NumberToWords::ordinal() writes the ordinals from 1 to 99. Persian has no options. Passing options with fa throws an exception.
Quick start#
<?php
require 'vendor/autoload.php';
use RtlyKit\Number\ArabicOptions;
use RtlyKit\Number\NumberToWords;
echo NumberToWords::convert(3, 'ar'), "\n"; // ثلاثة
echo NumberToWords::convert(1250, 'ar', ['mode' => 'noun', 'gender' => 'f']), "\n"; // ألف ومئتان وخمسون
echo NumberToWords::convert(-5, 'ar', new ArabicOptions(negative: 'ناقص')), "\n"; // ناقص خمسة
echo NumberToWords::ordinal(21), "\n"; // الحادي والعشرون
echo NumberToWords::ordinal(21, 'ar', ['gender' => 'f']), "\n"; // الحادية والعشرون
A wrong key, value or type in the options throws InvalidNumberException with the code invalid_argument. A raw TypeError or ValueError never escapes.
All options#
| Option | Values | Default | What it does |
|---|---|---|---|
mode | count, noun | count | count writes a bare number (answers, tables) and always uses the masculine forms. noun means a counted noun follows right after the number |
gender | m, f | m | The gender of the singular of the counted noun. Ignored in count mode |
case | nom, acc, gen | nom | The grammatical case of the whole number. It changes the letters of two, the tens, the hundreds-duals and the thousands-duals |
diacritics | none, case | none | case adds the case endings (short vowels and tanwin) |
hundreds | mi_a, ma_i_a | mi_a | The spelling مئة or مائة |
joinHundreds | true, false | true | ثلاثمئة (joined) or ثلاث مئة (spaced) |
negative | one word, 1 to 64 bytes | سالب | The word before a negative number. No spaces or control characters |
billion | milyar, bilyon | milyar | The name of 109: مليار or بليون |
definite | true, false | true | Ordinals only: with the article (الأول) or without it (أول) |
Each value also has a constant on ArabicOptions, such as ArabicOptions::MODE_NOUN, GENDER_FEMININE, CASE_GENITIVE, DIACRITICS_CASE, HUNDREDS_MA_I_A and BILLION_BILYON. ArabicOptions::fromArray() builds the object from an array.
Gender and counted nouns#
Set mode to noun when a counted noun follows the number, and give the gender of the singular of that noun. The numbers 3 to 10 take the opposite gender of the noun. The numbers 11 to 19 agree with it. The number 1, the number 2 and the units in 21 to 99 follow the noun's gender too.
$m = ['mode' => 'noun', 'gender' => 'm']; // masculine noun, for example كتاب
$f = ['mode' => 'noun', 'gender' => 'f']; // feminine noun, for example سيارة
echo NumberToWords::convert(3, 'ar', $m), "\n"; // ثلاثة
echo NumberToWords::convert(3, 'ar', $f), "\n"; // ثلاث
echo NumberToWords::convert(13, 'ar', $m), "\n"; // ثلاثة عشر
echo NumberToWords::convert(13, 'ar', $f), "\n"; // ثلاث عشرة
echo NumberToWords::convert(11, 'ar', $m), "\n"; // أحد عشر
echo NumberToWords::convert(11, 'ar', $f), "\n"; // إحدى عشرة
echo NumberToWords::convert(21, 'ar', $f), "\n"; // واحدة وعشرون
In noun mode, the last word of the phrase is in construct with the noun. So 200 becomes «مئتا», 2000 becomes «ألفا» and two million becomes «مليونا». You decide the gender of a plural or collective noun, because the library does not guess it. The number words never contain the noun. The numbers 1 and 2 follow their noun in Arabic, so you place them yourself.
echo NumberToWords::convert(200, 'ar'), "\n"; // مئتان
echo NumberToWords::convert(200, 'ar', ['mode' => 'noun']), "\n"; // مئتا
echo NumberToWords::convert(2000, 'ar', ['mode' => 'noun']), "\n"; // ألفا
Case#
The case option changes the letters of the words that change with case, even without vowel marks: اثنان and اثنين, عشرون and عشرين, مئتان and مئتين, ألفان and ألفين, and اثنا عشر and اثني عشر.
echo NumberToWords::convert(2, 'ar', ['case' => 'acc']), "\n"; // اثنين
echo NumberToWords::convert(20, 'ar', ['case' => 'gen']), "\n"; // عشرين
echo NumberToWords::convert(200, 'ar', ['case' => 'acc']), "\n"; // مئتين
echo NumberToWords::convert(2000, 'ar', ['case' => 'acc']), "\n"; // ألفين
echo NumberToWords::convert(12, 'ar', ['mode' => 'noun']), "\n"; // اثنا عشر
echo NumberToWords::convert(12, 'ar', ['mode' => 'noun', 'case' => 'gen']), "\n"; // اثني عشر
Vowel marks#
With diacritics set to case, the library writes the case ending on the last letter of each word that has one. It writes nothing else: no inner vowels and no hamza changes. The marks come in the standard order, with the vowel before the shadda.
echo NumberToWords::convert(125, 'ar', ['diacritics' => 'case']), "\n";
// مئةٌ وخمسةٌ وعشرونَ
echo NumberToWords::convert(125, 'ar', ['diacritics' => 'case', 'case' => 'acc']), "\n";
// مئةً وخمسةً وعشرينَ
echo NumberToWords::convert(125, 'ar', ['diacritics' => 'case', 'case' => 'gen', 'mode' => 'noun']), "\n";
// مئةٍ وخمسةٍ وعشرينَ
A word that is joined to a following noun or number takes the short vowel (damma, fatha or kasra). A word that is not joined takes tanwin. The tens take the endings «ـُونَ» and «ـِينَ». Vowel marks are not defined for ordinals, so ordinal() with diacritics: 'case' throws.
Hundreds and spelling#
echo NumberToWords::convert(300, 'ar'), "\n"; // ثلاثمئة
echo NumberToWords::convert(300, 'ar', ['hundreds' => 'ma_i_a']), "\n"; // ثلاثمائة
echo NumberToWords::convert(300, 'ar', ['joinHundreds' => false]), "\n"; // ثلاث مئة
echo NumberToWords::convert(800, 'ar', ['joinHundreds' => false]), "\n"; // ثماني مئة
echo NumberToWords::convert(100, 'ar', ['hundreds' => 'ma_i_a']), "\n"; // مائة
Both مئة and مائة are in use, and so are the joined and spaced forms. We chose the shorter, original spelling مئة and the joined form as defaults. The options give you the others.
Large numbers#
The library uses the short scale. The names are ألف, مليون, مليار (or بليون with billion: 'bilyon'), تريليون, كوادريليون, كوينتيليون, سكستيليون (1021) and سبتيليون (1024). The limit is every integer below 1027. The numbers 3 to 10 of a scale use the plural (آلاف, ملايين, مليارات, تريليونات and so on). Two of a scale is the singular with ان or ين.
echo NumberToWords::convert(5000, 'ar'), "\n"; // خمسة آلاف
echo NumberToWords::convert(11000, 'ar'), "\n"; // أحد عشر ألف
echo NumberToWords::convert(1000000000, 'ar'), "\n"; // مليار
echo NumberToWords::convert(1000000000, 'ar', ['billion' => 'bilyon']), "\n"; // بليون
echo NumberToWords::convert(2000000000, 'ar'), "\n"; // ملياران
echo NumberToWords::convert('1000000000000', 'ar'), "\n"; // تريليون
echo NumberToWords::convert('1000000000000000000000000', 'ar'), "\n"; // سبتيليون
echo NumberToWords::convert(102000, 'ar'), "\n"; // مئة ألف وألفان
The last line shows a choice we made. A group of hundreds plus 1 to 10 in front of a scale word is split into two parts, as in «مئة ألف وألفان» for 102 000. The shorter «مئة وألفان» could also be read as 100 plus 2000, so we avoid it. Groups with 11 to 99, or none, stay together («مئة وأحد عشر ألف» for 111 000).
A number of 1027 or more throws InvalidNumberException with the code number_too_large and the context limit = 10^27 - 1.
Negative numbers#
A negative number starts with «سالب». Use the negative option to change the word. Zero is never negative.
echo NumberToWords::convert(-5, 'ar'), "\n"; // سالب خمسة
echo NumberToWords::convert(-5, 'ar', ['negative' => 'ناقص']), "\n"; // ناقص خمسة
Ordinals from 1 to 99#
NumberToWords::ordinal($n, 'ar', $options) returns the ordinal. Its default locale is ar. The options that count are gender, case and definite. The numbers 11 to 19 have both parts agree with the noun, and have no case.
echo NumberToWords::ordinal(1), "\n"; // الأول
echo NumberToWords::ordinal(1, 'ar', ['gender' => 'f']), "\n"; // الأولى
echo NumberToWords::ordinal(3), "\n"; // الثالث
echo NumberToWords::ordinal(3, 'ar', ['gender' => 'f']), "\n"; // الثالثة
echo NumberToWords::ordinal(11), "\n"; // الحادي عشر
echo NumberToWords::ordinal(11, 'ar', ['gender' => 'f']), "\n"; // الحادية عشرة
echo NumberToWords::ordinal(21, 'ar', ['case' => 'gen']), "\n"; // الحادي والعشرين
echo NumberToWords::ordinal(2, 'ar', ['definite' => false]), "\n"; // ثان
echo NumberToWords::ordinal(2, 'ar', ['definite' => false, 'case' => 'acc']), "\n"; // ثانيا
echo NumberToWords::ordinal(99), "\n"; // التاسع والتسعون
Zero and negative numbers throw invalid_number. A number above 99 throws number_too_large. Another locale throws UnsupportedLocaleException. For Persian ordinals, use Format::ordinal() from Digits and number formatting.
Errors#
| Situation | Exception | Code |
|---|---|---|
| Unknown locale | UnsupportedLocaleException | unsupported_locale |
Option key, value or type invalid. Options with fa. diacritics on ordinals | InvalidNumberException | invalid_argument |
Not an integer, a fraction, NaN or INF | InvalidNumberException | invalid_number, non_finite_number |
| String over 4096 bytes | InvalidNumberException | input_too_long |
convert() of 1027 or more, ordinal above 99 | InvalidNumberException | number_too_large |
| Ordinal of 0 or less | InvalidNumberException | invalid_number |
try {
NumberToWords::convert(5, 'ar', ['gender' => 'x']);
} catch (\RtlyKit\Exceptions\InvalidNumberException $e) {
echo $e->getMessage(), ' [', $e->getErrorCode()->value, "]\n";
// Invalid option "gender": expected one of m, f. [invalid_argument]
}
Where the rules come from#
The forms follow Arabic grammar pages, a dictionary and one language-academy decision, all listed in the sources section. Known-answer tests cover each rule. A few outputs, such as the split groups for 102 000 and the plural forms of the large scale names, still wait for review by a native speaker.
Related: Number to words for Persian and the reverse direction, and Limits.