できない.dev

文字列を数値に変換するには

入力として受け取った文字列を整数や小数へ変換する基本形を各言語で示す。
変換に失敗したときの振る舞いが言語ごとに大きく違うため、例外・NaN・Result のどれで返るかまでを扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

raw = "42"
print(int(raw))                    # -> 42
print(float("3.14"))               # -> 3.14
print(int("1f", 16))               # 基数を指定 -> 31
print(int(" 42\n"))                # 前後の空白は無視される -> 42
 
try:
    int("42.0")                    # int は小数点付き文字列を受け付けない
except ValueError as e:
    print("ValueError:", e)        # -> invalid literal for int() with base 10: '42.0'
 
print(int(float("42.0")))          # 一度 float を経由する -> 42

int と float はそれぞれ整数と小数へ変換し、変換できない文字列には ValueError を投げる。
int は "42.0" のような小数点付きの文字列も受け付けないため、小数を含みうる入力は float を経由してから int にする。
第 2 引数の基数を使うと 16 進数などの文字列も直接読める。

うまくいかない時: Python で「TypeError: can only concatenate str (not "int") to str」が直らない

JavaScript 実行確認済み

console.log(Number("42"));          // 42
console.log(Number("3.14"));        // 3.14
console.log(Number(""));            // 0(空文字は 0 になる)
console.log(parseInt("42px", 10));  // 42(途中まで読む)
console.log(Number("42px"));        // NaN(全体が数値でないと NaN)
 
const n = Number("abc");
console.log(Number.isNaN(n));       // true
console.log(Number.isInteger(Number("42"))); // true

Number は文字列全体が数値として読めるときだけ数値を返し、読めなければ例外ではなく NaN を返す。
空文字が 0 になる点と、parseInt が "42px" のように途中まで読んで打ち切る点が食い違いの原因になりやすいので、入力検証では Number と Number.isNaN を組み合わせて判定する。

TypeScript 実行確認済み

const raw: string = "42";
const n: number = Number(raw);
console.log(n.toFixed(1)); // "42.0"
 
// 入力が数値かどうかを確かめてから number として扱う
function toNumber(value: string): number | null {
  const parsed: number = Number(value);
  return value.trim() !== "" && !Number.isNaN(parsed) ? parsed : null;
}
 
console.log(toNumber("3.14")); // 3.14
console.log(toNumber("abc"));  // null
console.log(toNumber(""));     // null

変換そのものは JavaScript と同じで、Number の戻り値が number として型付けされるだけである。
型の上では NaN も number なので、コンパイラは変換の失敗を教えてくれない。
失敗を呼び出し側に伝えたいなら、戻り値を number | null にするなど型で表現する必要がある。

うまくいかない時: TypeScript で「Argument of type ... is not assignable」が解消できない

Go 実行確認済み

package main
 
import (
	"fmt"
	"strconv"
)
 
func main() {
	n, err := strconv.Atoi("42")
	fmt.Println(n, err) // 42 <nil>
 
	f, _ := strconv.ParseFloat("3.14", 64)
	fmt.Println(f) // 3.14
 
	// 基数とビット幅を指定する汎用形
	h, _ := strconv.ParseInt("1f", 16, 64)
	fmt.Println(h) // 31
 
	if _, err := strconv.Atoi("42px"); err != nil {
		fmt.Println("err:", err) // strconv.Atoi: parsing "42px": invalid syntax
	}
}

strconv.Atoi が 10 進整数用の短縮形で、基数やビット幅を変えたいときは ParseInt や ParseFloat を使う。
戻り値の第 2 要素が error なので、変換の失敗はその場で受け取って処理する。
error を無視すると失敗時に 0 が入ったまま処理が進むため、入力由来の値では必ず確認する。

Rust 実行確認済み

fn main() {
    // parse は型注釈かターボフィッシュで変換先を決める
    let n: i32 = "42".parse().unwrap();
    println!("{}", n); // 42
 
    let f = "3.14".parse::<f64>().unwrap();
    println!("{}", f); // 3.14
 
    // 失敗する可能性は Result で返るので match か unwrap_or で受ける
    match "42px".parse::<i32>() {
        Ok(v) => println!("ok: {}", v),
        Err(e) => println!("err: {}", e), // invalid digit found in string
    }
 
    println!("{}", "abc".parse::<i32>().unwrap_or(0)); // 0
    println!("{}", i64::from_str_radix("1f", 16).unwrap()); // 31
}

parse は変換先の型を型注釈かターボフィッシュで指定し、結果を Result で返す。
失敗が型に現れるので、unwrap で落とすか unwrap_or で既定値にするか match で分岐するかを、その場で選ぶことになる。
16 進数などは from_str_radix を使う。

Java 実行確認済み

public class Main {
    public static void main(String[] args) {
        System.out.println(Integer.parseInt("42"));      // 42
        System.out.println(Double.parseDouble("3.14"));  // 3.14
        System.out.println(Integer.parseInt("1f", 16));  // 31
 
        try {
            Integer.parseInt("42.0");
        } catch (NumberFormatException e) {
            System.out.println("NumberFormatException: " + e.getMessage());
        }
 
        System.out.println(Integer.valueOf("42").getClass().getSimpleName()); // Integer
    }
}

Integer.parseInt と Double.parseDouble が基本形で、変換できない文字列には NumberFormatException を投げる。
parseInt は int を返し valueOf は Integer を返すという違いがあるため、null を扱いたいかどうかで選び分ける。
基数を指定する第 2 引数もある。

C# 実行確認済み

using System.Globalization;
 
Console.WriteLine(int.Parse("42"));                                    // 42
Console.WriteLine(double.Parse("3.14", CultureInfo.InvariantCulture)); // 3.14
 
// TryParse は例外を投げず、成否を bool で返す
if (int.TryParse("42px", out int parsed))
{
    Console.WriteLine(parsed);
}
else
{
    Console.WriteLine("変換できなかった");
}
 
Console.WriteLine(int.TryParse("42", out int ok) ? ok : -1);           // 42
Console.WriteLine(Convert.ToInt32("1f", 16));                          // 31

int.Parse は失敗すると例外を投げるのに対し、int.TryParse は成否を bool で返し、値は out パラメータへ入れる。
ユーザー入力のように失敗が想定される場面では TryParse を使うと例外処理を書かずに済む。
小数の解析は実行環境のロケールで区切り文字の解釈が変わるので、CultureInfo.InvariantCulture を明示する。

つまずき

失敗したときの返り方が言語ごとに違うことが、いちばんのつまずきである。
Python・Java は例外を投げ、Go は error を、Rust は Result を返し、JavaScript は例外も出さずに NaN を返す。
とくに JavaScript は失敗が値として静かに流れていくため、変換した数値をそのまま計算へ渡すと、結果が NaN になった場所と原因の場所が離れてしまう。
受け取った直後に判定するのが安全である。

途中まで読む変換に注意する

JavaScript の parseInt は先頭から数値として読める分だけを読み、"42px" を 42 として返す。
一方 Number("42px") は NaN になる。
単位付きの CSS 値を読むときには前者が便利だが、入力検証に使うと不正な文字列を通してしまう。
Go の strconv.Atoi や Rust の parse は文字列全体が数値でなければ失敗するので、検証の用途にはこちらの挙動のほうが向いている。

小数と整数を混同しない

Python の int は "42.0" を受け付けず ValueError になる。
Java の Integer.parseInt も同様に NumberFormatException を投げる。
CSV や JSON から読んだ値が整数のつもりでも小数表記で入っていることがあるため、整数が欲しい場合はいったん小数として読んでから丸めるか、入力側の書式を決めておく。
空白混じりの入力は先に取り除いておくと、原因の見えにくい失敗を減らせる。

この記事は役立ちましたか?