C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

文字列を数値に高速で変換する方法

[トピック内 2 記事 (1 - 2 表示)]  << 0 >>

■85086 / inTopicNo.1)  文字列を数値に高速で変換する方法
  
□投稿者/ ANA (1回)-(2017/09/11(Mon) 08:40:43)

分類:[.NET 全般] 



文字列を数値に変える方法について教えてください。


一般的には以下のようにして文字列を数値に変換するサンプルコードが書かれてあります。


Dim Num as Single = CSng("3.345235235235534543634") '14.3秒

10,000,000回Forループを繰り返し
計算時間を測定したのですが
この方法だと14.3秒かかりました。


一方で、

Dim Num as Single = CSng(Val("3.345235235235534543634")) '1.4秒

のようにして、Val関数で一度、文字列を数値にしてから
所望の数値形式に変換するようにすると
10倍程度速い1.4秒で計算が終わりました。


Single.Parse("3.345235235235534543634") '2.0秒
Convert.ToSingle("3.345235235235534543634") '2.0秒
CType("3.345235235235534543634", Single) '14.1秒

というのも試してみましたが、
やはり、
CSng(Val("3.345235235235534543634"))
が、もっとも速かったです。


それでいつもCSng(Val(の方法を使っているのですが、
このVal関数というのは、VB6以前の古いフォーマットだと思います。

そのため、何かの関数のラッパーになっているのではないかと思います。

VB.NETの方法を使ったもっと美しいコードがあれば教えてください。



引用返信 編集キー/
■85089 / inTopicNo.2)  Re[1]: 文字列を数値に高速で変換する方法
□投稿者/ 魔界の仮面弁士 (1407回)-(2017/09/11(Mon) 11:15:54)
No85086 (ANA さん) に返信
> そのため、何かの関数のラッパーになっているのではないかと思います。
すべて何某かのラッパーではあるので、どこまでを指しているのかにもよりますが:

> Dim Num as Single = CSng("3.345235235235534543634") '14.3秒
> CType("3.345235235235534543634", Single) '14.1秒
コンパイル後は、CSng(s) と CType(s, Single) は完全に等価ですので、
この 0.2 秒の差は測定誤差ということになりますね。


> Single.Parse("3.345235235235534543634") '2.0秒
> Convert.ToSingle("3.345235235235534543634") '2.0秒
この 2 つは別のメソッド呼び出しとしてコンパイルされますが、
処理速度が変わらないのは、どちらも内部的には
 If value Is Nothing Then
  Return 0.0F
 Else
  Return Single.Parse(value, CultureInfo.CurrentCulture)
 End If
に相当する同一の手順で処理されるためです。


一応、呼び出し回数を軽減させるために、
 Dim ci As CultureInfo = CultureInfo.CurrentCulture
 Dim ns As NumberStyles = NumberStyles.AllowLeadingWhite Or NumberStyles.AllowTrailingWhite Or NumberStyles.AllowLeadingSign Or NumberStyles.AllowDecimalPoint Or NumberStyles.AllowThousands Or NumberStyles.AllowExponent
 For n = 1 To 繰り返し回数
  Dim Num As Single = Single.Parse("3.345235235235534543634", ns, ci)
 Next
とすることはできます。もっとも、軽減したとしても、微々たる差でしょうから、
可読性を落としてまで実装する価値は無さそうですけれども。


> Dim Num as Single = CSng("3.345235235235534543634") '14.3秒
> Dim Num as Single = CSng(Val("3.345235235235534543634")) '1.4秒

CSng は「現在のカルチャー」に応じてその振る舞いを変化させますが、
Val はカルチャーを考慮せず、常に同じ動作になります。
そのため、Val を使った方が処理効率が早くなっています。

しかも後者の場合、実際には「VB の CSng 関数」が呼ばれることはありません。
Val 関数の戻り値が Double 型ということで、後者は VB の CSng ではなく、
直接「MSIL の 0x6b 命令」すなわち「conv.r4」に渡される形にコンパイルされます。


しかしながら、Double 型をいったん経由することになる以上は、
必ずしも、両者の結果が同一になるとは限りません。
たとえば "-4.4E+38" という文字列を処理した場合、
前者は OverflowException を発生させますが、
後者は Single.NegativeInfinity な値として格納されます。


> このVal関数というのは、VB6以前の古いフォーマットだと思います。
> VB.NETの方法を使ったもっと美しいコードがあれば教えてください。

個人的には、Val の利用を否定はしないですけれどね。
Microsoft.VisualBasic.Compatibility.VB6 名前空間に
所属しているわけでもないですし、今回のように
明確な意図があって使っているのならなおのこと。
# 意図的に Val を選択したことについては、コメント等で触れておいて欲しいですが。

ちなみに、
 ・Val では、小数点記号として "." しか認めない。CSng や Single.Parse はカルチャーを考慮する。
 ・Val("123D-2") は 1.23 を返すが、CSng("123D-2") や Single.Parse("123D-2") は例外を発する。
 ・全角の "(123)"を渡すと、Val は 0.0 を返し、CSngは -123.0 を返し、Single.Parse は例外を発する。
などの違いがあります。



> Dim Num as Single = CSng("3.345235235235534543634") '14.3秒
本題からは外れますが、そもそも Single 型で、
"3.3452352…" な値をとることはなかったりします。

3.345235235235534543634 を指定しても、内部値的には
3.3452353477478027343750 相当の値にしかならないためです。

ちなみに Single 精度では、下記のような非連続の近似値として格納されます。
 0b01000000010101100001100001010100 → 3.3452348709106445312500 相当(3.34523487)
 0b01000000010101100001100001010101 → 3.3452351093292236328125 相当(3.345235)
 0b01000000010101100001100001010110 → 3.3452353477478027343750 相当(3.34523535)
 0b01000000010101100001100001010111 → 3.3452355861663818359375 相当(3.34523559)
 0b01000000010101100001100001011000 → 3.3452358245849609375000 相当(3.34523582)

※左がバイナリ表現(二進小数化する前の値)、右が十進小数化後の値とラウンドトリップ表現。
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ