rs.nkmk.me

Rustで文字列を数値に変換(parse, from_str_radix)

Posted: | Tags: Rust, 文字列

Rustで文字列(String, &str)を数値(整数、浮動小数点数)に変換するにはparseメソッドを使う。整数型の関連関数from_str_radixを使うと、基数を指定して2進数や8進数、16進数表現の文字列を整数値に変換することも可能。

以下のサンプルコードでは簡単のためunwrapを使っている。必要であればその他のメソッドやmatchなどで処理すればよい。

文字列を数値に変換: parse

文字列を数値に変換するにはparseメソッドを使う。

数値の型Tparse::<T>()(ターボフィッシュ)で指定するか、変数の型注釈で指定する。

let s: &str = "100";
let i = s.parse::<i32>().unwrap();
assert_eq!(i, 100);

let i: i32 = s.parse().unwrap();
assert_eq!(i, 100);

parseが返すのはResult。数値に変換できない文字列の場合はエラーが返される。

let s: &str = "abc";
let is_err = s.parse::<i32>().is_err();
assert!(is_err);

parseは型強制によってStringからも使用可能。

let s: String = String::from("100");
let i = s.parse::<i32>().unwrap();
assert_eq!(i, 100);

なお、parseは数値に限らずFromStrトレイトを実装する任意の型に変換できる。

let s: &str = "false";
let b = s.parse::<bool>().unwrap();
assert_eq!(b, false);

浮動小数点数に変換できる文字列表現

浮動小数点数に変換できる文字列表現はf32およびf64from_strのドキュメントに記載されている。f32f64も同じ。

‘3.14’
‘-3.14’
‘2.5E10’, or equivalently, ‘2.5e10’
‘2.5E-10’
‘5.’
‘.5’, or, equivalently, ‘0.5’
‘inf’, ‘-inf’, ‘+infinity’, ‘NaN’
f64::from_str - Rust

eを使った指数表記のほか、無限大infや非数NaNにも変換可能。アルファベットの大文字小文字は区別されない(例えば"inf"でも"INF"でも"inF"でもよい)。

assert_eq!("1.23".parse::<f64>().unwrap(), 1.23);
assert_eq!("123".parse::<f64>().unwrap(), 123.0);
assert_eq!("123.".parse::<f64>().unwrap(), 123.0);
assert_eq!(".123".parse::<f64>().unwrap(), 0.123);
assert_eq!("1.23e2".parse::<f64>().unwrap(), 123.0);
assert_eq!("1.23e-2".parse::<f64>().unwrap(), 0.0123);

assert_eq!("inf".parse::<f64>().unwrap(), f64::INFINITY);
assert_eq!("-inf".parse::<f64>().unwrap(), f64::NEG_INFINITY);
assert_eq!("infinity".parse::<f64>().unwrap(), f64::INFINITY);
assert!("nan".parse::<f64>().unwrap().is_nan());

基数を指定して2進数・8進数・16進数を変換: from_str_radix

整数型(u32i32など)の関連関数from_str_radixを使うと、基数を指定して文字列を整数に変換できる。

2進数や8進数、16進数表現の文字列を整数に変換可能。

let s: &str = "100";
assert_eq!(i32::from_str_radix(s, 2).unwrap(), 4);
assert_eq!(i32::from_str_radix(s, 8).unwrap(), 64);
assert_eq!(i32::from_str_radix(s, 16).unwrap(), 256);

基数には2以上36以下の値を指定できる。

This function panics if radix is not in the range from 2 to 36. i64::from_str_radix - Rust

第一引数の型は&strなので、Stringの場合は参照を指定する。

let s: String = String::from("ff");
assert_eq!(i32::from_str_radix(&s, 16).unwrap(), 255);

アルファベットは大文字でも小文字でもよい。

プレフィックス(0b, 0o, 0x)を判定して変換

from_str_radixはプレフィックス(0b, 0o, 0x)を考慮しない。

プレフィックス付きの文字列は3文字目以降を渡せばよい。ASCII文字のみを考慮すればいいため、スライスで部分文字列を取得できる。

let s: &str = "0xFF";
assert_eq!(i32::from_str_radix(&s[2..], 16).unwrap(), 255);

以下は、文字列のプレフィックスから基数を決定して整数値に変換する関数の例。なお、以降の例もそうだが、もっといい書き方があるかもしれない。

文字列の2文字目を取得し基数を決定する。エラー処理には外部クレートのanyhowを使っている。

use anyhow::{bail, ensure, Result};

fn parse_with_prefix(s: &str) -> Result<usize> {
    ensure!(s.len() > 2, "too short");

    let radix = match s.chars().nth(1).unwrap().to_ascii_lowercase() {
        'b' => 2,
        'o' => 8,
        'x' => 16,
        _ => bail!("unexpected radix"),
    };

    Ok(usize::from_str_radix(&s[2..], radix)?)
}
assert_eq!(parse_with_prefix("0b100").unwrap(), 4);
assert_eq!(parse_with_prefix("0o100").unwrap(), 64);
assert_eq!(parse_with_prefix("0x100").unwrap(), 256);

assert!(parse_with_prefix("0x").is_err());
assert!(parse_with_prefix("0a100").is_err());
assert!(parse_with_prefix("0xZZZ").is_err());

上の例では決め打ちでusize::from_str_radixを使っている。

あまり必要ないかもしれないが、ジェネリックな関数にもできる。標準ライブラリstdではFromStrRadixのようなトレイトが提供されていないため、外部クレートのnum_traitsを利用する。

use num_traits::Num;

fn parse_with_prefix_generic<T>(s: &str) -> Result<T>
where
    T: Num,
    <T as Num>::FromStrRadixErr: std::error::Error + Send + Sync + 'static,
{
    ensure!(s.len() > 2, "too short");

    let radix = match s.chars().nth(1).unwrap().to_ascii_lowercase() {
        'b' => 2,
        'o' => 8,
        'x' => 16,
        _ => bail!("unexpected radix"),
    };

    Ok(<T as Num>::from_str_radix(&s[2..], radix)?)
}
assert_eq!(parse_with_prefix_generic::<i16>("0xFF").unwrap(), 255_i16);
assert_eq!(parse_with_prefix_generic::<u128>("0xFF").unwrap(), 255_u128);

基数を固定してプレフィックスの有無に関わらず変換

基数を固定してプレフィックス有り無しどちらも変換したい場合もある。以下は16進数の例。

fn parse_hex(s: &str) -> Result<usize, std::num::ParseIntError> {
    const RADIX: u32 = 16;
    const PREFIX_L: &str = "0x";
    const PREFIX_U: &str = "0X";

    if s.len() < 3 {
        usize::from_str_radix(s, RADIX)
    } else if s.starts_with(PREFIX_L) || s.starts_with(PREFIX_U) {
        usize::from_str_radix(&s[2..], RADIX)
    } else {
        usize::from_str_radix(s, RADIX)
    }
}
assert_eq!(parse_hex("0xFF").unwrap(), 255);
assert_eq!(parse_hex("F").unwrap(), 15);
assert_eq!(parse_hex("100").unwrap(), 256);

assert!(parse_hex("0x").is_err());
assert!(parse_hex("0xZZZ").is_err());
assert!(parse_hex("xyz").is_err());

例は省略するが、2進数や8進数も同様。

なお、例えば0b100がプレフィックスあり2進数なのか、プレフィックスなし16進数なのかを判定できないため、プレフィックスの有無か基数のどちらかは決定していなければならない。

関連カテゴリー

関連記事