[[20181018220803]] 『閑話:サロゲートペアに対するLEN関数、LEFT関数の』(中途B) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『閑話:サロゲートペアに対するLEN関数、LEFT関数の結果について』(中途B)

 質問というか、ご意見を頂戴したく・・・という類の内容です。(暇潰し的なものとお考えください)

 先日こちらで私のつまらない質問にアドバイスを頂いて以降、バイト配列の勉強をしています。
 なのでテーマ自体は少々マニアックかも知れませんが、知識は追い付けてない素人だと思って下さい。
 (ホントはバイト配列の勉強自体が既に脱線なのですが、今回はそのまた脱線です^^;)

 新規ブック標準モジュールに以下のコードをコピペして頂き、[Test]を実行した結果についてです。

    Function szChrW(cCode As Variant) As String
        Dim b() As Byte
        If cCode > &H10000 Then
            Dim H As Long, L As Long
            ReDim b(0 To 3) As Byte
            H = (cCode - &H10000) \ &H400& + &HD800&
            L = (cCode - &H10000) Mod &H400& + &HDC00&
            b(1) = H \ &H100&: b(0) = H Mod &H100&
            b(3) = L \ &H100&: b(2) = L Mod &H100&
        Else
            ReDim b(0 To 1) As Byte
            b(1) = cCode \ &H100&: b(0) = cCode Mod &H100&
        End If
        szChrW = b
    End Function
    Function szAscW(aChar As String) As Long
        Dim b() As Byte
        b = aChar
        If b(1) >= &HD8& Then 'High:&HD800~&HDBFF, Low:&HDC00~&HDFFF
            szAscW = ((b(1) * &H100& + b(0)) - &HD800&) * &H400& + ((b(3) * &H100& + b(2)) - &HDC00&) + &H10000
        Else
            szAscW = b(1) * &H100& + b(0)
        End If
    End Function
    Function vbLen(String1 As String) As Long
        vbLen = Len(String1)
    End Function
    Function vbLenB(String1 As String) As Long
        vbLenB = LenB(String1)
    End Function
    Function vbLeft(String1 As String, Length As Long) As String
        vbLeft = Left$(String1, Length)
    End Function
    Function vbLeftB(String1 As String, Length As Long) As String
        vbLeftB = LeftB$(String1, Length)
    End Function
    Function Str2ByteHex(String1 As String) As String
        Dim b() As Byte, i As Long
        b = String1
        For i = 0 To UBound(b)
            Str2ByteHex = Str2ByteHex & Right$(Hex(&H100& + b(i)), 2)
            Str2ByteHex = Str2ByteHex & IIf(i Mod 2, "|", ",")
        Next
    End Function
    Function Str2SJIS(String1 As String) As String
        Str2SJIS = StrConv(String1, vbFromUnicode)
    End Function

    Sub Test()
        [A1:A10] = [{"コード";"文字";"LEN";"LEFT";"LENB";"LEFTB";"vbLen";"vbLeft";"vbLenB";"vbLeftB"}]
        [b1] = 128246
        [b2].Formula = "=szChrW(B1)"
        [b3].Formula = "=LEN(B2)"
        [B4].Formula = "=LEFT(B2,1)"
        [B5].Formula = "=LENB(B2)"
        [B6].Formula = "=LEFTB(B2,1)"
        [B7].Formula = "=vbLen(B2)"
        [B8].Formula = "=vbLeft(B2,1)"
        [B9].Formula = "=vbLenb(B2)"
        [B10].Formula = "=vbLeftb(B2,1)"
        [c2].Formula = "=Str2ByteHex(B2)"
        [c2].Copy [C4,C6,C8,C10]
        Columns(3).AutoFit
        [D2].Formula = "=Str2ByteHex(Str2SJIS(B2))"
        [D2].Copy [D4,D6,D8,D10]
        [E4] = "LEFTで1文字分取り出すと、LENでいう2文字分が返った"
        [E6] = "LEFTBで1バイト分取り出すと、LENBでいう2バイト分が返った"
        [E8] = "上位サロゲートのみ。Lenの結果とも整合性がとれる"
        [E10] = "上位サロゲートの下位バイトのみが取り出され、上位バイトはゼロとして評価されたのだろう"
    End Sub

 VBAのLen、LenB、Left、LeftBについては、いずれも「2バイトで1文字」として計算されており、
 サロゲートペアを判定せずに計算しているであろう結果そのものは納得できるものです。

 対してWorksheetFunction側は、
 LEN関数が2文字と判断したのであれば、LEFT関数で1文字取り出した結果が元のままなのはおかしいし、
 LEFT関数で1文字取り出した結果が元と同じなのであれば、LEN関数は1文字と判断して欲しいところです。

 少なくともLEN関数とLEFT関数では文字列長に対する計算仮定が異なるという事になります。
 例) LEN関数はShift-JISで判断して「??」で2文字。あるいはUnicodeのままサロゲートペアを判定せず2文字。
     LEFT関数はUnicodeのままサロゲートペア1組で1文字

 そしてLENB関数が2バイトと判断したのであれば、やはりShift-JISで判断して「??」で2バイト
 ところがLEFTB関数で1バイト取り出した結果までもが元と同じです。

 LEFT関数はUnicodeのままサロゲートペア1組で1文字と判断したと仮定しても、
 LEFTB関数はサロゲートペアどころか、バイト長すら無視した結果です。
  

 関数の内部仕様が公開されている訳でもないし、そもそもサロゲートペアに対応してないんでしょうから、
 「そんな所に疑問を持っても仕方ないよ」っては自覚しています。
 ただ、分からないなりの合理的解釈というかなんというか・・・
 「こんな考え方もできるのでは?」みたいなご意見が頂きたいなと思ってます。

 宜しければお付き合い願います。

< 使用 Excel:Excel2010、使用 OS:Windows7 >


OSの対応に合わせて、Excelも追加分のUnicodeの入力や表示には対応したけれど、関数やマクロは対応してない、という事だと思いますよ。

シートのLenBが1になってしまうのは、2byteのUnicode以外だから「?」として変換してしまい、半角1文字だから1なのでしょう。(表示はShiftJISだから)
VBAのLenBが2なのは、やはり「?」で半角1文字なので、2なのでしょう。(内部処理はUnicodeだから)
「a」なんかでも同様の結果になります。

WINDOWS以外の世界は、UTF-8によるマルチバイト表現が主流になりつつあるので、どうせ設計を変えるならこっちだろうし、Unicodeの拡張のためだけに頑張ってOfficeの基本設計から変えても割に合わなそうなので、拡張Unicodeを関数でうまく扱えない状況は変わらないんじゃないかと思います。(英語圏では何も困ってないし!、とか思っていそう)
(???) 2018/10/19(金) 10:07


 ???さん書き込みありがとうございます。
 >WINDOWS以外の世界は、UTF-8によるマルチバイト表現が主流になりつつある

絵文字がある種のUnicodeバグを世界から一掃しつつある件について|Rui Ueyama|note
https://note.mu/ruiu/n/nc9d93a45c2ec

 ↑勉強中に辿り着いたサイトですが、ここでも
 >ある意味、ここ数年でコンピュータの歴史上で初めて、
 >英語圏ですらASCIIの範囲内では日常的に文字が足りないという状況になったともいえる。

 >絵文字などを使うと結局可変長になってしまうのだから、
 >UTF-16ではなく最初からUTF-8を使う方がいいじゃないかという認識が以前より広まったように思う。

 という意見を書いてありますね。
  

 追加で確認してみた事です。
 例えば[aあ📶1](←表示大丈夫なのかな?)という文字列はバイト配列(Unicodeのまま)で表すと
 |61,00|42,30|3D,D8|F6,DC|31,00|

 になりますが、
 これをMID(文字列,1,ROW(A1))で取り出すと
 ROW  |バイト
 1    |61,00|
 2    |61,00|42,30|
 3    |61,00|42,30|3D,D8|
 4    |61,00|42,30|3D,D8|F6,DC|
 5    |61,00|42,30|3D,D8|F6,DC|31,00|

 同じくLEFT(文字列,ROW(A1))の場合
 ROW  |結果
 1    |61,00|
 2    |61,00|42,30|
 3    |61,00|42,30|3D,D8|F6,DC|
 4    |61,00|42,30|3D,D8|F6,DC|31,00|

 MID関数とLEFT関数でも考え方に違いがある様ですね。
 MID関数の方はVBAのMidと同じ結果になります。

 但し同じ事をMIDB関数、LEFTB関数でやってみると、
 MIDB(文字列,1,ROW(A1))の場合
 ROW  |結果
 1    |61,00|
 2    |61,00|20,00|                   <--「あ」の前半は半角スペースに補正された
 3    |61,00|42,30|
 4    |61,00|42,30|3D,D8|             <--やはり「??」扱いで判定か?
 5    |61,00|42,30|3D,D8|F6,DC|
 6    |61,00|42,30|3D,D8|F6,DC|31,00|

 LEFTB(文字列,ROW(A1))の場合
 ROW  |結果
 1    |61,00|
 2    |61,00|20,00|                   <--「あ」の前半は半角スペースに補正された
 3    |61,00|42,30|
 4    |61,00|42,30|3D,D8|F6,DC|       <--LEFT関数と同じ挙動
 5    |61,00|42,30|3D,D8|F6,DC|31,00|

 やはりLEFTB関数の結果はサロゲートペア部分がLEFT関数と同じ。

 Shift-JISとかバイト長とか関係なく、まずは数式バー内でのキャレットの移動単位で1文字を識別し、
 サロゲートペアが出たら、ある意味処理をスキップしてしまい、
 結果「1バイト1文字」として処理されたかの様になっているのかも知れませんね。
 と、勝手に想像しています。
 RIGHT関数とRIGHTB関数についても試してみましたが同じ挙動の様です。

 MID関数の方は開始位置を計算する必要があるから、バイト長を無視する訳にもいかないでしょうし。

 どこかで「LEFTなんてMIDの応用だろ」という思い込みがあったので、
 内部でやってる事は全然違うっぽいな、というのを今回気付かされました。

(中途B) 2018/10/19(金) 21:38


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.