Skip to content

Why Numbers Look Reversed in Arabic AI Output

9 min read · updated August 11, 2026

A model returns an Arabic sentence with an invoice number or a date in it, and the digits come out in the wrong order on screen. Before changing anything, work out which of five situations you are in — in two of them the text is correct and the renderer is doing exactly what the standard requires.

What you are actually seeing

Write down the complaint precisely, because the five causes produce different complaints and the wrong fix for any of them makes the text worse rather than better.

  • The digits of one number are in reverse order 1234 shows as 4321. This is almost never bidi. Bidi never reverses the digits inside a single number.
  • Groups of digits swapped, digits within each intact 2026-08-11 shows as 11-08-2026. This is the algorithm doing what it is defined to do, and it is the interesting case.
  • The number is on the wrong side of the sentence — correct digits, wrong position. That is a neutral-resolution question, covered in mixed Arabic and English text.
  • The digits are the wrong shape — U+0660 to U+0669 where you expected ASCII, or the reverse. That is a numbering-system question, not a direction one; see Arabic-Indic numerals in AI output.
  • It only looks wrong in one place — correct in the browser, reversed in the PDF or the terminal. That is the consuming format having no bidi implementation.

Why digits are left-to-right inside RTL text

The Unicode Bidirectional Algorithm, specified in Unicode Standard Annex #9, assigns every character a bidi class and then assigns every character an embedding level. Even levels run left-to-right, odd levels run right-to-left. Arabic letters have class AL, ASCII digits have class EN (European Number), and Arabic-Indic digits U+0660–U+0669 have class AN (Arabic Number).

The implicit-level rules are where the behaviour comes from. In a right-to-left paragraph the base level is 1. Rule I2 says that at an odd level, characters of class L, EN and AN are raised by one — to level 2, which is even, and therefore laid out left-to-right. So a number inside Arabic text sits in a small left-to-right island inside a right-to-left line. That is not a bug and not a workaround; it is how Arabic has always been written. The number one hundred and twenty-three reads with the hundreds digit on the left, in Arabic just as in English.

One more rule earns its keep here. Rule W2 changes class EN to AN when the most recent strong character was an Arabic letter. This means ASCII digits appearing after Arabic text are treated as Arabic numbers for the rest of the algorithm, which changes how the separators around them resolve. It is the reason the same digit string behaves differently depending on what precedes it in the paragraph.

The real bug: separated digit groups

Here is the case that genuinely surprises people, written in UAX #9 notation where uppercase stands for right-to-left characters.

storage (logical) order:   ARABIC 2026-08-11 ARABIC
display in an RTL paragraph:  CIBARA 11-08-2026 CIBARA

The digits inside each group are intact. The groups swapped. The cause is the hyphen. U+002D HYPHEN-MINUS has class ES (European Separator), and rule W4 only allows a single ES between two EN runs to become EN — joining them into one number. That works for 2026-08 in isolation. But once W2 has converted the digits to AN because the last strong character was Arabic, W4 no longer applies: an ES between two AN runs is not absorbed. Rule W6 turns the leftover separator into a neutral, and rules N1/N2 then give that neutral the paragraph level — right-to-left. You now have three left-to-right islands separated by right-to-left glue, and the islands are laid out in right-to-left order. The date reverses at the field level.

The same mechanism hits phone numbers with spaces or hyphens, version strings, IP addresses, ranges like 10-20, and any identifier that is digits-punctuation-digits. It is entirely deterministic: given the same string in the same paragraph direction, every conforming implementation produces the same wrong-looking result. There is nothing to reproduce intermittently.

Double reordering, and reversing the string

The other family of causes has nothing to do with the algorithm and everything to do with the pipeline running it twice, or running it in reverse.

Visual-order source. Text extracted from a PDF, or from a legacy system, is often already in visual order — the reordering was baked in when the document was produced. Feed that to a renderer that applies the bidi algorithm and it reorders text that was already reordered, which is what actually turns 1234 into 4321. If your text came out of a PDF, start at reordering extracted Arabic text rather than here.

A hand-rolled “RTL fix”. Somewhere in almost every codebase that has struggled with this there is a function that calls reverse() on the string, or on each word, to make Arabic “work” in some component that had no bidi support. It produces plausible output for pure Arabic prose and destroys every number in it. Grep for it before you do anything else.

Character-level reversal by a font or shaper. Rarer, and it shows as connected Arabic letters appearing in the wrong order too, not only digits. If the letters are also affected, the problem is below the text layer.

The fix, in order of preference

For the separated-groups case, the answer is to tell the renderer that the whole identifier is one left-to-right unit. In HTML the correct tool is isolation, not an embedding and not a direction override.

<!-- best: the element carries the meaning -->
<p dir="rtl">... <bdi dir="ltr">2026-08-11</bdi> ...</p>

<!-- equivalent, if you must use a span -->
<span dir="ltr" style="unicode-bidi: isolate">2026-08-11</span>

<!-- plain text, no markup available: -->
<!-- U+2066 LRI ... U+2069 PDI -->
"\u2066" + "2026-08-11" + "\u2069"

Isolate rather than embed. An isolate makes the enclosed run opaque to the surrounding text — the algorithm treats it as a single neutral object — so it cannot pull the neighbouring punctuation around with it. An embedding (U+202A/U+202B) leaves the boundaries interacting, and an override (U+202D/U+202E) forces direction character by character and will mangle anything genuinely bidirectional inside it. The isolate characters were added to Unicode specifically because embeddings could not express “this is a self-contained run”.

Two things not to do. Do not insert U+200E LEFT-TO-RIGHT MARK into the stored data: it is invisible, it will not match in a database lookup or an equality check, and it will spread through your system as the canonical value of an invoice number. Keep control characters at the presentation boundary. And do not reformat the date to avoid the problem — the same paragraph will eventually contain a version string or a phone number, and you will be back here.