数値の操作

Takami Torao Java 9 Scala 2.12 Julia 0.6 #java #scala #julialang
  • このエントリーをはてなブックマークに追加

数値の変換

数値と文字列の変換

Java

jshell> Integer.toString(12345)
$1 ==> "12345"

jshell> String.valueOf(12345)
$2 ==> "12345"

jshell> String.valueOf(123.45)
$3 ==> "123.45"

jshell> Integer.parseInt("12345")
$4 ==> 12345

jshell> Double.parseDouble("123.45")
$5 ==> 123.45

各ラッパークラスに用意されている toString() または String.valueOf() で文字列に変換する。文字列から数値へは parseXXX() を使用する。

Scala

scala> 123.45.toString
res20: String = 123.45

scala> "12345".toInt
res21: Int = 12345

scala> "123.45".toDouble
res22: Double = 123.45

数値型、文字列型に定義されている toString, toInt 等を使用する。

Julia

julia> string(123.45)
"123.45"

julia> parse(Int32, "12345")
12345

julia> parse(Float64, "123.45")
123.45

標準関数の string(), parse() を使用する。

数値に変換できない文字列の対応

Java

jshell> int x = 0;
x ==> 0

jshell> try {
   ...>   x = Integer.parseInt("123456789xyz");
   ...> } catch(NumberFormatException ex){
   ...>   // 変換できなかった場合の処理
   ...>   System.err.println(ex);
   ...> }
java.lang.NumberFormatException: For input string: "123456789xyz"

変換処理を try-catch で囲み失敗したときの処理を catch ブロックに記述する。

Julia

julia> x = try parse(Int32, "foo") catch "error" end
"error"

julia> x = tryparse(Int32, "foo")
Nullable{Int32}()

julia> isnull(x)
true

tryparse() を使って null をチェックするか、変換処理を try-catch で囲み変換に失敗したときの処理を catch ブロックに記述する。

Scala

scala> import scala.util.{Try,Success,Failure}
import scala.util.{Try,Success,Failure}

scala> val x = Try("foo".toInt).getOrElse(-1)
x: Int = -1

scala> Try("foo".toInt) match {
 |   case Success(x) => println(x)   /* 成功時の処理 */
 |   case Failure(ex) => println(ex) /* 失敗時の処理 */
 | }
java.lang.NumberFormatException: For input string: "foo"

Java と同様に try-catch を使うことも可能だが Try を使うのが一般的。getOrElse() で失敗時にデフォルト値を返すだけでなく、match で成功時の処理と失敗時の処理を記述したり、成功したかだけを判断する isSuccess など記述のバリエーションが豊富。

数値と2進数、8進数、16進数、任意進数の変換

Java

jshell> Integer.toBinaryString(12345)
$13 ==> "11000000111001"

jshell> Integer.toOctalString(12345)
$14 ==> "30071"

jshell> Integer.toHexString(12345)
$15 ==> "3039"

jshell> Integer.toString(12345, 7)
$10 ==> "50664"

jshell> Character.MAX_RADIX
$11 ==> 36

jshell> for(int i=0; i<Character.MAX_RADIX; i++) System.out.print(Integer.toString(i, Character.MAX_RADIX))
0123456789abcdefghijklmnopqrstuvwxyz

Java は Integer クラスの toXxxString() メソッドを使用して各進数の文字列へ変換を行う。toString() に指定できる基数の最大値は Character.MAX_RADIX まで。

jshell> Integer.parseInt("50664", 7)
$8 ==> 12345

jshell> Integer.parseInt("9ix", Character.MAX_RADIX)
$9 ==> 12345

文字列から整数への変換は基数指定の Integer.parseInt() を使用する。ただし変換可能な基数の最大値は Character.MAX_RADIX まで。

Scala

scala> 12345.toBinaryString
res26: String = 11000000111001

scala> 12345.toHexString
res27: String = 3039

scala> 12345.toOctalString
res30: String = 30071

Scala は 2, 8, 16 進数であれば整数型に定義されている toXxxString を使用することができる。それ以外は Java と同じ。

Julia

julia> bin(12345)
"11000000111001"

julia> hex(12345)
"3039"

julia> oct(12345)
"30071"

julia> base(7, 12345)
"50664"

julia> for i in 0:35 print(base(36, i)) end
0123456789abcdefghijklmnopqrstuvwxyz

julia> for i in 0:61 print(base(62, i)) end
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Julia は 2, 8, 16 進数にそれぞれ標準関数が用意されている。基数はアルファベットの大文字と小文字を区別することで 62 まで使用することができるが、基数が 36 を超えると 10-36 部分が大文字で扱われるようになる点に注意。

julia> parse(Int32, "11000000111001", 2)
12345

julia> parse(Int32, "3039", 16)
12345

julia> parse(Int32, "50664", 7)
12345

julia> parse(Int32, "3D7", 62)
12345

Julia は parse() 関数に基数を指定することで任意の基数を数値に変換することができる。基数に指定できる最大値は 62 まで。

数値をコンマ区切りの文字列に変換

Java

int x = 123456789;
String s1 = String.format("%,d", x);  // "123,456,789"

import java.text.NumberFormat;
NumberFormat nf = NumberFormat.getInstance();
String s2 = nf.format(x);     // "123,456,789"

Java の標準機能 Locale により数値の区切りが3桁でない国や区切り文字がコンマでない国でも文化圏に合わせた動作を行う (例えばドイツは , の代わりに . を使う)。

Scala

scala> val x = 123456789
x: Int = 123456789

scala> f"comma => $x%,d"
res2: String = comma => 123,456,789

scala> "comma => %,d".format(x)
res3: String = comma => 123,456,789

Scala は Java と同じ機能が使える他に f"..." で文字列内に式と書式を埋め込む String Interpolation が使用できる。

Julia

julia> Pkg.add("Formatting")
INFO: Initializing package repository C:\Users\Takami Torao\.julia\v0.6
INFO: Cloning METADATA from https://github.com/JuliaLang/METADATA.jl
INFO: Cloning cache of Formatting from https://github.com/JuliaIO/Formatting.jl.git
INFO: Installing Formatting v0.3.0
INFO: Package database updated

julia> using Formatting
INFO: Precompiling module Formatting.

julia> s = sprintf1("%'d", x)
"123,456,789"

julia> s = format(x, commas=true)
"123,456,789"

Formatting パッケージを使用することでコンマ区切りの書式を利用できる。Formatting.jl 参照。

数値を小数点以下の桁数を指定した文字列に変換

Java

jshell> String.format("%.3f", Math.PI)
$6 ==> "3.142"

jshell> import java.util.Locale
jshell> Locale.setDefault(Locale.GERMAN)
jshell> String.format("%.3f", Math.PI)
$9 ==> "3,142"

String クラスの format() を使用する。このメソッドは実行環境の言語に合わせた小数点を使用する (例えばドイツは . の代わりに , を使う)。ほかに数値フォーマットを指定する DecimalFormat も使用できる。

Scala

scala> f"${math.Pi}%.3f"
res18: String = 3.142

scala> "%.3f".format(math.Pi)
res19: String = 3.142

Scala は Java と同じ機能が使えるが f"..." で文字列内に式と書式を埋め込む String Interpolation を使用するのが一般的。

Julia

julia> Pkg.add("Formatting")
INFO: Package Formatting is already installed

julia> using Formatting

julia> x = Float64(pi)
3.141592653589793

julia> s = sprintf1("%.3f", x)
"3.142"

julia> format(x, precision=3)
"3.142"

Formatting パッケージを使用することでコンマ区切りの書式を利用できる (Pkg.add() は初回のみで良い)。Formatting.jl 参照。

高精度の数値から低精度の数値へ変換

Java

jshell> double f64 = Math.PI
f64 ==> 3.141592653589793

jshell> float f32 = f64
|  エラー:
|  不適合な型: 精度が失われる可能性があるdoubleからfloatへの変換
|  float f32 = f64;
|              ^-^

jshell> float f32 = (float)f64
f32 ==> 3.1415927

jshell> long i64 = 1234567890123456789L
i64 ==> 1234567890123456789

jshell> int i32 = i64
|  エラー:
|  不適合な型: 精度が失われる可能性があるlongからintへの変換
|  int i32 = i64;
|            ^-^

jshell> int i32 = (int)i64
i32 ==> 2112454933

低精度側の型 float, int などへ明示的なキャストをする。

Scala

scala> val f64:Double = math.Pi
f64: Double = 3.141592653589793

scala> val f32:Float = f64
<console>:12: error: type mismatch;
 found   : Double
 required: Float
       val f32:Float = f64
                       ^
scala> val f32:Float = f64.toFloat
f32: Float = 3.1415927

scala> val i64:Long = 100
i64: Long = 100

scala> val i32:Int = i64.toInt
i32: Int = 100

Scala は Java のようなキャストがない代わりに明示的に Double.toFloatLong.toInt メソッドを使用して変換する。

Julia

julia> f64 = pi
π = 3.1415926535897...

julia> f32 = Float32(f64)
3.1415927f0

julia> i64 = Int64(100)
100

julia> i32 = Int32(i64)
100

動的型付け言語のため変数に型宣言はない。関数に渡すために精度を下げる場合は標準の Float32()Int32() などを使用する。

数値とバイト配列の変換

Java (Scala)

jshell> import java.nio.ByteBuffer
jshell> import java.nio.ByteOrder

jshell> byte[] b1 = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.BIG_ENDIAN).putLong(1234567890L).array()
b1 ==> byte[8] { 0, 0, 0, 0, 73, -106, 2, -46 }

jshell> long i1 = ByteBuffer.wrap(b1).order(ByteOrder.BIG_ENDIAN).getLong()
i1 ==> 1234567890

jshell> byte[] b2 = ByteBuffer.allocate(Double.BYTES).order(ByteOrder.LITTLE_ENDIAN).putDouble(1234.56789).array()
b2 ==> byte[8] { -25, -58, -12, -124, 69, 74, -109, 64 }

jshell> double f2 = ByteBuffer.wrap(b2).order(ByteOrder.LITTLE_ENDIAN).getDouble()
f2 ==> 1234.56789

型と同じバイト数の ByteBuffer をメモリに割り当て値を put して配列を参照する。バイトオーダーは作成するバイト配列をどちらにしたいかで変更可能 (デフォルトは BE)。Scala も同様。

Julia

julia> out = IOBuffer(Int(Int64.size))
IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=true, append=false, size=0, maxsize=8, ptr=1, mark=-1)

julia> write(out, 1234567890)
8

julia> b = out.data
8-element Array{UInt8,1}:0xd2 0x02 0x96 0x49 0x00 0x00 0x00 0x00

julia> in = IOBuffer(b)
IOBuffer(data=UInt8[...], readable=true, writable=false, seekable=true, append=false, size=8, maxsize=Inf, ptr=1, mark=-1)

julia> read(in, Int64)
1234567890

IOBuffer に数値を write() する。バイトオーダーは実行環境に依存するため BE, LE どちらかに固定したければ変換処理が必要。

julia> hex2bytes(num2hex(1234567890))
8-element Array{UInt8,1}: 0x00 0x00 0x00 0x00 0x49 0x96 0x02 0xd2

julia> hex2bytes(num2hex(1234.567890))
8-element Array{UInt8,1}: 0x40 0x93 0x4a 0x45 0x84 0xf4 0xc6 0xe7

処理効率は落ちるが一度16進数文字列に変換したあとにバイト配列へ変換する簡便な方法もある (逆の変換も可能)。この方法のバイトオーダーは BE となる。

バイトオーダーの変換

実行環境の CPU アーキテクチャによって整数が上位バイトを上位アドレスに持つか (LE) 下位アドレスに持つか (BE) が決まっている。この並び順のことを バイトオーダー (byte order) と呼ぶ。異なるアーキテクチャのコンピュータ間でネットワークやファイルを介してバイナリデータをやりとりする場合、その仕様で決められたバイトオーダーを使わなければならない。ここではそのバイトオーダーの変換方法を記述する。なお BE = Big Endian, LE = Little Endian の意味。

Java (Scala)

jshell> import java.nio.ByteBuffer
jshell> import java.nio.ByteOrder

jshell> ByteBuffer.allocate(Long.BYTES)
          .order(ByteOrder.BIG_ENDIAN)
          .putLong(1234567890123456789L)
          .flip()
          .order(ByteOrder.LITTLE_ENDIAN)
          .getLong()
$19 ==> 1549776473967043089

JavaVM はどのようなコンピュータ上で動いていても BE が標準。バイト配列への変換と同様に一度 BE で書き込んで flip して LE で読み出せばバイトオーダーが逆転される。

jshell> long flipByteOrder(long num){
  long x = 0;
  for(int i=0; i<Long.BYTES; i++){
    long y = num & (0xFFL << (i*8));
    int shift = (Long.BYTES-i-1-i)*8;
    x |= (shift >= 0)? (y << shift): (y >> -shift);
  }
  return x;
}
|  次を変更しました: メソッド flipByteOrder(long)

jshell> flipByteOrder(1234567890123456789L)
$36 ==> 1549776473967043089 

バイトオーダーの変換程度にヒープを使いたくなければ上記のようにビット演算を用いて記述してもよい。

Julia

julia> hton(1234567890123456789)
1549776473967043089

julia> htol(1234567890123456789)
1234567890123456789

Julia では Unix C/C++ で標準的な Berkeley Sockets と同じバイトオーダー変換用の関数が利用できる。

ホストバイトオーダーとは実行環境のバイトオーダーのことで、これが BE か LE かは実行環境に依存する (例えば Intel x86 なら LE, Sun SPARC なら BE)。ネットワークバイトオーダーは BE のこと。つまり hton() は実行環境の整数値を BE に変換する (実行環境が BE なら何もしない)。同様に実行環境の整数値を LE に変換する場合は htol() を使用する。

BE, LE からホストバイトオーダーに変換する場合は ntoh(), ltoh() 関数を使用する。