整数を2進数や16進数の文字列にするには
255 を 11111111 や ff のように、2 進数・8 進数・16 進数の文字列にする方法と、その文字列を整数へ戻す方法を各言語で示す。
桁数のそろえ方、負の数の出力、0x などの接頭辞の扱いまでを扱う。
公開:
選んだ言語は、ほかのサンプルコードを開いたときも最初に表示する。
Python
n = 255
print(bin(n), oct(n), hex(n)) # 0b11111111 0o377 0xff
print(format(n, "b"), format(n, "o"), format(n, "x")) # 11111111 377 ff
print(f"{n:X}") # FF
print(f"{5:08b}") # 00000101(幅 8 を 0 で埋める)
print(f"{n:#012b}") # 0b0011111111(接頭辞も幅 12 に数える)
print(format(-5, "b"), bin(-5)) # -101 -0b101(2 の補数にならない)
print(int("ff", 16), int("11111111", 2)) # 255 255
print(int("0xff", 16), int("0xff", 0)) # 255 255
try:
print("0x" + 255)
except TypeError as e:
print(e) # can only concatenate str (not "int") to str
print("0x" + format(255, "x")) # 0xff
try:
int("0xff")
except ValueError as e:
print(e) # invalid literal for int() with base 10: '0xff'format(n, "b") や f-string の {n:08b} で整数を 2 進数・8 進数・16 進数の文字列にできる。bin と hex は 0b 0x の接頭辞付きで返り、文字列に戻すときは int(text, 16) のように基数を渡す。"0x" + 255 のように数値を + でつなぐと TypeError になるため、format で文字列にしてからつなぐ。
JavaScript
const n = 255;
console.log(n.toString(2), n.toString(8), n.toString(16)); // 11111111 377 ff
console.log(n.toString(16).toUpperCase()); // FF
console.log((5).toString(2).padStart(8, "0")); // 00000101
console.log("0x" + n.toString(16)); // 0xff
console.log((-5).toString(2)); // -101(2 の補数にならない)
console.log((-5 >>> 0).toString(2)); // 11111111111111111111111111111011(32 ビットの補数)
console.log(parseInt("ff", 16), parseInt("11111111", 2)); // 255 255
console.log(parseInt("0xff", 16), parseInt("0xff")); // 255 255
console.log(Number("0xff"), Number("0b101")); // 255 5
console.log(parseInt("0b101", 2)); // 0(b で読み取りが止まる)
console.log(parseInt("102", 2)); // 2(2 進数に無い桁の手前までを読む)
console.log(parseInt("12", 2), Number("12abc")); // 1 NaNNumber.prototype.toString に基数を渡すと 2 進数から 36 進数までの文字列になり、戻すときは parseInt(text, radix) を使う。parseInt は基数に無い文字の手前で読み取りを止めて例外にしないため、parseInt("102", 2) は 2 を返す。
外部の入力は事前に検証する。
TypeScript
function toRadix(value: number, radix: number, width = 0): string {
if (!Number.isInteger(value)) {
throw new RangeError(`整数ではない: ${value}`);
}
return value.toString(radix).padStart(width, "0");
}
function fromRadix(text: string, radix: number): number {
const digits = "0123456789abcdefghijklmnopqrstuvwxyz".slice(0, radix);
if (!new RegExp(`^[${digits}]+$`, "i").test(text)) {
throw new RangeError(`${radix} 進数として読めない: ${text}`);
}
return parseInt(text, radix);
}
console.log(toRadix(255, 2)); // 11111111
console.log(toRadix(255, 16)); // ff
console.log(toRadix(5, 2, 8)); // 00000101
console.log(fromRadix("ff", 16)); // 255
console.log(fromRadix("1010", 2)); // 10
try {
fromRadix("102", 2);
} catch (e) {
console.log((e as Error).message); // 2 進数として読めない: 102
}
console.log(parseInt("102", 2)); // 2(標準の parseInt は黙って途中までを読む)toString(radix) と padStart の組み合わせを、整数だけを受け付ける関数にまとめた。
標準の parseInt は不正な桁を黙って読み捨てるので、正規表現で桁を先に検証してから戻している。
Go
package main
import (
"fmt"
"strconv"
)
func main() {
n := 255
fmt.Println(strconv.FormatInt(int64(n), 2), strconv.FormatInt(int64(n), 8), strconv.FormatInt(int64(n), 16)) // 11111111 377 ff
fmt.Printf("%b %o %x %X\n", n, n, n, n) // 11111111 377 ff FF
fmt.Printf("%08b\n", 5) // 00000101
fmt.Printf("%#x %#b\n", n, n) // 0xff 0b11111111
fmt.Println(strconv.FormatInt(-5, 2)) // -101(2 の補数にならない)
m := int32(-5)
fmt.Printf("%b\n", uint32(m)) // 11111111111111111111111111111011(uint32 にすると 2 の補数)
a, _ := strconv.ParseInt("ff", 16, 64)
b, _ := strconv.ParseInt("11111111", 2, 64)
c, _ := strconv.ParseInt("0xff", 0, 64)
fmt.Println(a, b, c) // 255 255 255
_, err := strconv.ParseInt("0xff", 16, 64)
fmt.Println(err) // strconv.ParseInt: parsing "0xff": invalid syntax
_, err = strconv.ParseInt("1ff", 16, 8)
fmt.Println(err) // strconv.ParseInt: parsing "1ff": value out of range
}strconv.FormatInt と fmt の書式指定 %b %o %x で文字列にし、strconv.ParseInt(text, base, bitSize) で戻す。ParseInt は 0x 付きの文字列を基数 16 では読めずエラーにするので、接頭辞ごと読むなら基数に 0 を渡す。
桁数が bitSize に収まらないときもエラーになる。
Rust
fn main() {
let n: i32 = 255;
println!("{:b} {:o} {:x} {:X}", n, n, n, n); // 11111111 377 ff FF
println!("{:08b}", 5); // 00000101
println!("{:#x} {:#b}", n, n); // 0xff 0b11111111
println!("{:#012b}", n); // 0b0011111111(接頭辞も幅 12 に数える)
println!("{:b}", -5i32); // 11111111111111111111111111111011(2 の補数)
println!("{:x}", -255i32); // ffffff01
println!("{:?}", i64::from_str_radix("ff", 16)); // Ok(255)
println!("{:?}", i64::from_str_radix("11111111", 2)); // Ok(255)
println!("{:?}", i64::from_str_radix("0xff", 16)); // Err(ParseIntError { kind: InvalidDigit })
println!("{:?}", i64::from_str_radix("0xff".trim_start_matches("0x"), 16)); // Ok(255)
println!("{:?}", i8::from_str_radix("1ff", 16)); // Err(ParseIntError { kind: PosOverflow })
match u8::from_str_radix("zz", 16) {
Ok(v) => println!("{}", v),
Err(e) => println!("{}", e), // invalid digit found in string
}
}書式指定の {:b} {:o} {:x} で文字列にし、from_str_radix(text, radix) で戻す。
接頭辞付きにするには {:#x} と書く。
負の数は 2 の補数のビット列になるほか、from_str_radix は 0x を読めず InvalidDigit を返すため、先に trim_start_matches で取り除く。
Java
public class Main {
public static void main(String[] args) {
int n = 255;
System.out.println(Integer.toBinaryString(n) + " " + Integer.toOctalString(n) + " " + Integer.toHexString(n)); // 11111111 377 ff
System.out.println(Integer.toString(n, 16)); // ff
System.out.println(Integer.toHexString(n).toUpperCase()); // FF
System.out.println(String.format("%x %X %o", n, n, n)); // ff FF 377
System.out.println(String.format("%8s", Integer.toBinaryString(5)).replace(' ', '0')); // 00000101
System.out.println(Integer.toBinaryString(-5)); // 11111111111111111111111111111011(2 の補数)
System.out.println(Integer.toString(-5, 2)); // -101
System.out.println(Integer.parseInt("ff", 16) + " " + Integer.parseInt("11111111", 2)); // 255 255
System.out.println(Integer.decode("0xff")); // 255
try {
Integer.parseInt("0xff", 16);
} catch (NumberFormatException e) {
System.out.println(e.getMessage()); // For input string: "0xff" under radix 16
}
try {
Integer.parseInt("ffffffff", 16);
} catch (NumberFormatException e) {
System.out.println(e.getMessage()); // For input string: "ffffffff" under radix 16
}
System.out.println(Integer.parseUnsignedInt("ffffffff", 16)); // -1
}
}Integer.toBinaryString toOctalString toHexString で文字列にし、Integer.parseInt(text, radix) で戻す。toString(n, radix) は負の数を -101 のように符号付きで返すが、toBinaryString は 32 ビットの 2 の補数を返す。parseInt は 0x を読めないので、接頭辞付きなら Integer.decode を使う。
C#
using System;
using System.Globalization;
int n = 255;
Console.WriteLine(Convert.ToString(n, 2) + " " + Convert.ToString(n, 8) + " " + Convert.ToString(n, 16)); // 11111111 377 ff
Console.WriteLine(n.ToString("x") + " " + n.ToString("X")); // ff FF
Console.WriteLine(n.ToString("X4")); // 00FF
Console.WriteLine(Convert.ToString(5, 2).PadLeft(8, '0')); // 00000101
Console.WriteLine(Convert.ToString(-5, 2)); // 11111111111111111111111111111011(32 ビットの補数)
Console.WriteLine((-5).ToString("X")); // FFFFFFFB(符号なしの 16 進数に見える)
Console.WriteLine(Convert.ToInt32("ff", 16) + " " + Convert.ToInt32("11111111", 2)); // 255 255
Console.WriteLine(Convert.ToInt32("0xff", 16)); // 255
Console.WriteLine(int.Parse("ff", NumberStyles.HexNumber)); // 255
try { Convert.ToInt32("zz", 16); }
catch (FormatException e) { Console.WriteLine(e.Message); } // Could not find any recognizable digits.
try { int.Parse("0xff", NumberStyles.HexNumber); }
catch (FormatException e) { Console.WriteLine(e.Message); } // The input string '0xff' was not in a correct format.Convert.ToString(n, 2) で 2・8・10・16 進数の文字列にし、Convert.ToInt32(text, 16) で戻す。n.ToString("X4") のような書式指定なら 4 桁にそろえた大文字の 16 進数になる。
基数に使えるのは 2・8・10・16 だけで、負の数は 32 ビットの 2 の補数で出力される。
つまずき
整数の 2 進数・16 進数表記は、基数を指定して整数を文字列にする関数(Python の format、JavaScript の toString(radix)、Go の strconv.FormatInt、Java の Integer.toString(n, radix)、C# の Convert.ToString)か、書式指定(%b %x {:b} {:x})で作る。
戻すときは基数を渡して読む関数(int(text, 16)、parseInt(text, 16)、strconv.ParseInt、from_str_radix、Integer.parseInt、Convert.ToInt32)を使う。
255 は 2 進数で 11111111、8 進数で 377、16 進数で ff になり、7 言語とも実行して確認した。
負の数は言語で出力が変わる
-5 を 2 進数にすると、言語によって 2 通りの結果になった。
-101(符号と絶対値):Python のformat(-5, "b")、JavaScript の(-5).toString(2)、Go のstrconv.FormatInt(-5, 2)、Java のInteger.toString(-5, 2)11111111111111111111111111111011(32 ビットの 2 の補数):Rust の{:b}、Java のInteger.toBinaryString、C# のConvert.ToString(-5, 2)。Go でもuint32に変換してから%bで出すと同じ並びになる
C# の (-5).ToString("X") は FFFFFFFB を返し、符号なしの値のように見える。
ビット列を見たいのか、数値としての表記を見たいのかを先に決め、負の数が入りうるなら出力を実行して確かめる。
JavaScript で 2 の補数を得たいときは (-5 >>> 0).toString(2) と書く。
接頭辞と桁数のそろえ方
0x や 0b の接頭辞は、文字列にする側と読む側で扱いが違う。
- 文字列にする側:Python の
binとhexは接頭辞付きで返す。Go・Rust・Python の書式指定は#を付けたとき(%#x、{:#x}、{n:#b})だけ接頭辞が付く。それ以外の関数は付けない - 読む側:Python の
int("0xff", 16)、JavaScript のparseInt("0xff", 16)、C# のConvert.ToInt32("0xff", 16)は接頭辞を許して 255 を返す。Go のParseInt("0xff", 16, 64)はinvalid syntax、Rust のfrom_str_radixはInvalidDigit、Java のInteger.parseIntはNumberFormatException、C# のint.Parse(…, NumberStyles.HexNumber)はFormatExceptionになる
Go と Python は基数に 0 を渡し、Java は Integer.decode を使うと接頭辞から基数を判断して読める。
桁数をそろえるときは、Python・Go・Rust の 08b、JavaScript の padStart(8, "0")、C# の PadLeft か X4 を使う。
Java の String.format は 2 進数の文字列に 0 を詰めるフラグが無いため、%8s で空白埋めしてから replace(' ', '0') で置き換えた。
不正な入力と範囲外の値
基数に無い文字を含む文字列を読ませたときの挙動も、言語で分かれた。
- 例外やエラーにする:Go の
ParseInt、Rust のfrom_str_radix、Java のparseInt、C# のConvert.ToInt32(zzを渡すとCould not find any recognizable digits.) - 黙って途中までを読む:JavaScript の
parseInt("102", 2)は 2、parseInt("0b101", 2)は 0 を返す。TypeScript の例のように、正規表現で桁を先に検証する
値が型に収まらないときも違いがあり、Go の ParseInt("1ff", 16, 8) は value out of range、Rust の i8::from_str_radix("1ff", 16) は PosOverflow を返した。
Java で ffffffff を parseInt で読むと NumberFormatException になるが、Integer.parseUnsignedInt("ffffffff", 16) なら -1 として読める。
ビットパターンを扱うときは符号なしの読み取りを使う。