Text Normalization › Full-Width to Half-Width Conversion
Language AI HandbookAll chapters Text Normalization › Full-Width to Half-Width ConversionOne section · Sections in this chapter8 / 15 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
East Asian text often uses full-width versions of ASCII characters. These take up the same width as CJK characters, creating visual alignment in mixed text. A product name like "IBM" uses full-width Latin letters so that the three characters align with three CJK characters in adjacent rows. In typographic contexts, this is a deliberate design choice. In NLP contexts, it is a source of variation that prevents matching.
The full-width ASCII characters occupy the Unicode block from U+FF01 to U+FF60, directly mirroring the printable ASCII characters from U+0021 to U+0060. The mapping is a simple arithmetic offset: subtract 0xFEE0 from any full-width character to get its half-width equivalent. This regularity makes conversion straightforward, and it means you can implement the conversion without any lookup table.
# Full-width ASCII characters
fullwidth_examples = [
("A", "A", "Full-width A"),
("a", "a", "Full-width a"),
("0", "0", "Full-width 0"),
("!", "!", "Full-width exclamation"),
(" ", " ", "Ideographic space"),
]
# Full-width to half-width conversion
def fullwidth_to_halfwidth(text):
"""Convert full-width ASCII to half-width."""
result = []
for char in text:
code = ord(char)
# Full-width ASCII range: U+FF01 to U+FF5E maps to U+0021 to U+007E
if 0xFF01 <= code <= 0xFF5E:
result.append(chr(code - 0xFF01 + 0x21))
# Ideographic space to regular space
elif code == 0x3000:
result.append(" ")
else:
result.append(char)
return "".join(result)
# Test conversion
fullwidth_text = "Hello World! 123"
halfwidth_text = fullwidth_to_halfwidth(fullwidth_text)Full-Width to Half-Width Conversion: Full-width: 'Hello World! 123' Half-width: 'Hello World! 123' Character-by-character: 'H' (U+FF28) -> 'H' (U+0048) 'e' (U+FF45) -> 'e' (U+0065) 'l' (U+FF4C) -> 'l' (U+006C) 'l' (U+FF4C) -> 'l' (U+006C) 'o' (U+FF4F) -> 'o' (U+006F)
Each full-width character maps to its ASCII equivalent by subtracting a fixed offset (0xFEE0) from the code point. The ideographic space (U+3000) is a special case that maps to the regular space (U+0020). This conversion is essential when processing East Asian text that mixes CJK characters with Latin letters and digits. A company name like "ABC株式会社" should match "ABC株式会社" in any reasonable search system.
NFKC normalization also handles full-width to half-width conversion automatically:
# NFKC also converts full-width characters
nfkc_result = unicodedata.normalize("NFKC", fullwidth_text)
manual_match = nfkc_result == halfwidth_textNFKC vs manual conversion: NFKC result: 'Hello World! 123' Manual result: 'Hello World! 123' Match: True
The NFKC normalization produces identical results to the manual conversion function, confirming that NFKC handles full-width to half-width mapping as part of its compatibility normalization. This means you can use NFKC for broad compatibility normalization without implementing character-specific conversion logic, simplifying your normalization pipeline considerably. The manual implementation is shown here to make the underlying mapping explicit, but in practice NFKC is the preferred approach because it handles all compatibility mappings in a single pass.