YorozuyaIT
Japanese Text & Encoding

Shift_JIS vs CP932: Why ~ Becomes ? and Python Raises UnicodeEncodeError

Written by the Yorozuya IT editorial team, based in Japan. Menus, apps, and rules change, so check the date.

Short answer

Two things trigger this problem. One is an error such as UnicodeEncodeError: 'shift_jis' codec can't encode character '\uff5e'. The other is text that silently turns into ? after a round trip through a Windows system. Both come from treating Shift_JIS and CP932 as the same thing.

They are close, not identical

Shift_JIS is the JIS-standard encoding. CP932, also called Windows-31J, is Microsoft's variant: it keeps the same byte layout but maps a few bytes to different characters and adds extra ones. Windows software uses CP932 even when its menus say "Shift_JIS".

Table showing the same byte pairs mapped to different characters by shift_jis and cp932

The famous case is the wave dash. Bytes 81 60 are 〜 (U+301C) in Python's shift_jis codec and ~ (U+FF5E, fullwidth tilde) in cp932. They look nearly the same, but they are different characters. So encoding ~ with shift_jis fails, and a 〜 read from a standards-based system may show up as ? or a different glyph on Windows.

You can see it yourself:

b = bytes([0x81, 0x60])
print(hex(ord(b.decode("shift_jis"))))   # 0x301c  〜
print(hex(ord(b.decode("cp932"))))       # 0xff5e  ~

"~".encode("cp932")      # works
"~".encode("shift_jis")  # UnicodeEncodeError

What else differs

Bytes shift_jis cp932
81 7C − (U+2212 minus) - (U+FF0D fullwidth hyphen-minus)
81 61 ‖ (U+2016) ∥ (U+2225)
81 91 ¢ (U+00A2) ¢ (U+FFE0)

CP932 also contains characters that strict Shift_JIS lacks, including circled numbers ①, the company symbol ㈱, and the variant kanji 髙 and 﨑 that appear in real people's names. Encoding these with shift_jis raises UnicodeEncodeError.

Which codec should you use?

Fixing the error on write

If you hit the error and the destination expects Shift_JIS-style data, switch to cp932 first. If you really need strict shift_jis, convert the problem characters beforehand:

FIX = str.maketrans({
    "\uff5e": "\u301c",   # ~ -> 〜
    "\uff0d": "\u2212",   # - -> −
    "\u2225": "\u2016",   # ∥ -> ‖
    "\uffe0": "\u00a2",   # ¢ -> ¢
})

text = text.translate(FIX)
data = text.encode("shift_jis")

Characters with no equivalent (such as ①) still fail. Decide per case: replace them (errors="replace"), expand them (① → 1), or stop and use UTF-8.

Avoiding silent damage

The worst case is not an error but a quiet ?. To catch it, round-trip a sample: encode, decode, and compare with the original. If they differ, your chosen encoding loses data.

Quick checklist

FAQ

Which should I pick for a Japanese CSV?

UTF-8 whenever the reader supports it. If the reader needs a legacy encoding for Windows or Excel, use cp932, not shift_jis.

What are the Java names?

Java's Shift_JIS is the standard mapping. The Windows variant is windows-31j, with the alias MS932.

Do databases have the same split?

MySQL has both sjis and cp932 character sets. The same differences apply, so choose cp932 if the data comes from Windows.

Keep reading