match式による構造破壊マッチング

match式による構造破壊マッチング

match式のパターンマッチ内において、値を構造破壊することが出来る。

Destructuring Tuples

タプル値を構造破壊してパターンマッチ

let triple = (0, -2, 3);

match triple {
    (0, y, z) => plintln!("First is `0`, `y` is {:?}, `z` is {:?}", y, z),
    (1, ..)   => println!("First is `1`, and the rest doesn't matter"),
    _         => println!("It doesn't matter what they are"),
}

Destructuring enums

enum値を構造破壊してパターンマッチ

#[allow(dead_code)]
enum Color {
    Red,
    Blue,
    Green,
    RGB(u32, u32, u32),
    CMYK(u32, u32, u32, u32),
}

fn main() {
    let color = Color::RGB(122, 17, 40);

    match color {
        Color::Red  => println!("The color is Red!"),
        Color::Blue => println!("The color is Blue!"),
        Color::Green => println!("The color is Green!"),
        Color::RGB(r, g, b)  => println!("Red: {}, Green: {}, Blue: {}", r, g, b),
        Color::CMYK(c, m, y, k) => println!("Cyan: {}, Magenta: {}, Yellow: {}, key (Black): {}!", c, m, y, k),

    }
}

Destructurring pointer

パターンマッチによるデリファレンスが出来る

fn main() {
    let x = 4;
    let reference = &x;
    match reference {
        // シングル参照とマッチング、=>右辺は、デリファレンスされる
        &val => println!("Got a value via destructuring: {:?}", val),
    }
}

ダブル参照もデリファレンス可能

fn main() {
    let x = 4;
    let reference = &&x;
    match reference {
        // ダブル参照をマッチング、=>右辺は、デリファレンスされる
        &&val => println!("Got a value via destructuring: {:?}", val),
    }
}

matchの前に、*演算子によるdereferenceも化

match *reference {
    val => println!("Got a value via derefereencing: {:?}", val),
}

refキーワードを付けることで、右側が&演算子がついてなくても参照として受け取ることが出来る

// refキーワード
let ref reference = 3; // &i32
match *reference {
    val => println!("Got a value via derefereencing: {:?}", val),
}

参照型として値を受け取る

fn main() {
    let value = 5;
    match value {
        // rは参照型として受け取れる
        ref r => println!("Got a reference to a value: {:?}", r),
    }
}

ref mutによって、match式内部で値を変更できる

let mut mut_value = value;
match mut_value {
    ref mut m => {
        *m += 10;
    },
}
println!("We added 10. `mut_value`: {:?}", mut_value);

Destructuring struct

match式によって構造体の解体も可能

fn main() {
    struct Foo {
        x: (u32, u32),
        y: u32,
    }

    let foo = Foo { x: (1, 2), y: 3 };

    match foo {
        Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y),

        // 順番は関係しない
        Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i),

        // いくつかの変数を無視できる
        Foo { y, .. } => println!("y = {}, we don't care about x", y),
    }
}

参照

https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring.html

型の種類とプリミティブデータ型

型の種類

  • T: 通常型
  • &T: 参照型 - ある所有された値への借用を表す型
  • [T; N] : 配列型
  • [T] : スライス型
  • *const T : ポインタ型
  • (T, U, V) : タプル型
  • fn(T) -> V : 関数型

プリミティブデータ型

bool

char

一つのユニコードスカラー値 (4バイト表現)

let x = 'x';
let two_heart = '💕';

Numeric types

  • i8
  • i16
  • i32
  • i64
  • u8
  • u16
  • u32
  • isize: ポインタ型のサイズ,32bitCPUならば4バイト、64bitCPUならば8バイト
  • usize: indexに指定出来る値
  • f32
  • f64

Arrays [T; N]

配列型

let a = [1, 2, 3]; // a: [i32; 3];
let mut m = [1, 2, 3]; // m: [i32; 3];

要素の確保と0埋め

let a = [0; 20]; // a[i32; 20];
println!("{}", a.len());

Slices

スライス型

[T] / &mut [T]

配列の一部へコピーを発生させないで、安全に効率的なアクセスをする。 動的Sized型であるため、参照型(値の借用)である必要がある。

let a = [1, 2, 3, 4]

// 配列からスライスを生成する
let complete = &a[..];
let middle = &a[1..4];

str

文字列型の最もプリミティブな型を表す。

Tuples

タプルは、固定長の順序つきリスト

let x = (1, "hello") // x: (i32, &str)

destructuring let

let (x, y, z) = (1, 2, 3);
dbg!(x);

タプルインデクシング

let tuple = (1, 2, 3);
let x = tuple.0;
let y = tuple.1;
let z = tuple.2;

Functions

関数も型。 (クロージャーはランタイムで自動生成される)

fn foo(x: i32) -> i32 { x }

fn main() {
    let x: fn(i32) -> i32 = foo;
}

参照

http://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/primitive-types.html

型 作成と実装

型(Type)の作成と実装

Rustは型(Type)の作成と実装が分かれる。 (最近はトレイトも実はサイズを持たない型(Type)と言えるのではないかと思うようになった) 型の作成は、struct, enumを使い、実装は impl を使う。

型の作成 struct enum

Struct

struct Point {
    x: i32,
    y: i32,
    z: i32,
}

Enum

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

Trait

trait Human {
    fn get_name() -> String;
    fn get_age() -> i32;
}

トレイトは振る舞いを定義できる型(Type)

型の実装 impl

impl

型にメソッドを追加する

impl Point {
    fn add(&self, other: Point) -> Point {
        Point(self.x + other.x, self.y + other.y, self.z + other.z)
    }
}
impl Ipaddress {
    fn equal(&self, other) -> Bool {
    }
}

参照型とポインタ型

参照型とポインタ型

  • 参照型 &T / &mut T - ある所有された値への借用を表す型
  • ポインタ型 *const T / *mut T - nullを表すことができる

ポインタ型を使うメリットとデメリット

Rustではポインタ型は、値がnullの可能性がある。 したがって扱う場合は、unsafeブロック内部で行う必要がある。 その代わり、Cポインタのように配列の走査が行える。

ポインタ型

変数宣言時に、初期化せずに、nullを代入することが出来る。 ただし、参照外し(*演算子を使って)がされた場合は、非nullで、かつ初期化された値を返す必要がある。

fn main() {
    let p: *const u8;
    unsafe {
        p = "ABC".as_ptr();
        println!("{:?}", *p as char); // u8からcharにキャスト
        println!("{:?}", *p.add(1) as char);
        println!("{:?}", *p.add(2) as char);
    }
}
  • *p: 参照外しに寄ってu8に変換

ポインタ型の作り方

1. 参照強制(&T or &mut T)による生成

let my_num: i32 = 10;
let ptr_my_num: *const i32 = &my_num; // アノテーションを付ける必要がある

let mut my_speed: i32 = 88;
let ptr_my_speed: *mut i32 = &mut my_speed;

NOTE:

2. Box型から値を取り出して生成

let my_num: Box<i32> = Box::new(10);
let p_my_num: *const i32 = &*my_num;

NOTE:

  • Box型はスマートポインタなので、*演算子(参照外し演算子) によって内部の値を取り出せる
  • &演算子によってポインタ型に変換している

3. Cから取得する

extern crate libc;

use std::mem;

unsafe {
    let my_num: *mut i32 = libc::malloc(mem::size_of::<i32>()) as *mut i32;
    if my_num.is_null() {
        panic!("failed to allocate memory");
    }
    libc::free(my_num as *mut libc::c_void);
}

一般的に、 Rustからmalloc,freeを直接使わない。 しかし、C APIはたくさんのポインタを手渡しするため、Rustにおいてもポインタとして扱う。

ポインタ型の使い方

配列走査などをポインタ型で行うと、処理速度の向上が見込めるが、nullもしくは未初期化やオーバーフローしている可能性があるため、unsafeブロック内部で処理を記述する必要がある。

値の取得

fn main() {
    let s: &str = "ABC";
    let ptr: *const u8 = s.as_ptr();

    unsafe {
        println!("{}", *ptr); // 先頭ポインタ(u8)の値を指し示す。型はu8。
        println!("{}", *ptr.offset(0) as char); // A
        println!("{}", *ptr.offset(1) as char); // B
    }
}
  • *演算子
    • ポインタが指し示している先頭の値を指し示す
    • unsafeブロック内部で使用する必要がある
  • offset(self, count: isize)
    • ポインタからのオフセットを計算する
    • countの単位は、size_of::<T>()バイト毎になる
    • コンビニエンスメソッドとして、add(count: usize) , sub(count: usize)が存在する
let s: &str = "ABC";
unsafe {
    let end: *const u8 = s.as_ptr().add(3); 
    println!("{}", *end as char); // LF(0xA)を指し示している
    println!("{}", *end.sub(1) as char); // C
    println!("{}", *end.sub(2) as char); // B
}
  • addに指定するcountによっては、オーバーフローしている可能性がある

値の走査

文字の検索

fn main() {
    let text = "A language empowering everyone to build reliable and efficient software.";
    let word = "everyone";

    let p_text: *const u8 = text.as_ptr();
    let p_word: *const u8 = word.as_ptr();
    let mut exist = false;
    'i: for i in 0..text.len() {
        'j: for j in 0..word.len() {
            unsafe {
                if *p_text.add(i + j)  != *p_word.add(j) {
                    break 'j;
                }
            }
            if j == word.len() - 1 {
                exist = true;
                break 'i;
            }
        }
    }
    println!("result = {}", exist);
}

関連

スマートポインタ https://yossan.hatenablog.com/entry/2020/09/20/224329

Dynamically Sized Type (DST) https://yossan.hatenablog.com/entry/2020/10/11/230422

参照

Primitive Type pointer https://doc.rust-lang.org/std/primitive.pointer.html

Iterator::collect()

Iterator::collect()

fn collect<B>(self) -> B
where
    B: FromIterator<Self::Item>, 

IteratorをCollectionに変換する。 collect()で変換できるCollectionはFromIterator<Self::Item>トレイトが実装されている必要がある。

Trait std::iter::FromIterator

pub trait FromIterator<A> {
    fn from_iter<T>(iter: T) -> Self 
    where
        T: IntoIterator<Item = A>;
}

Iteratorから型を生成できることを表す。 FromIterator::from_iter()メソッドを直接呼ぶことはなく、Iterator::collect()メソッドを通して使用する。

反対のトレイトとして、IntoIteratorがある。

Vec<_> への変換

impl<T> FromIterator<T> for Vec<T>
let five_fives = std::iter::repeat(5).take(5);
let v: Vec<i32> = five_fives.collect();

[char]からStringの生成

impl FromIterator<char> for String
let chars = ['h', 'e', 'l', 'l', 'o'];
let text = chars.iter().collect::<String>();

単精度浮動小数点数への変換

123

  1. 2進数に変換

    0b1111011

  2. 単精度浮動小数点数形式に正規化

    0b1.111011 x 2^(6)

  3. 単精度浮動小数点数データ構造に変換

    • 符号部: 0
    • 指数部: 6 + 127 = 134
    • 仮引数部: 11101100000000000000000

    Swiftによる確認

 let f = 123 as Float
    print("符号: ", f.sign)
    print("指数: ",  f.exponent)
    print("指数部: ", f.exponentBitPattern)
    print("仮引数部: ", String(f.significandBitPattern, radix:2));
    /*
    符号:  plus
    指数:  6
    指数部:  133
    仮引数部:  11101100000000000000000
    */

123.625

  1. 2進数に変換する

    0b1111011.101

  2. 単精度浮動小数点数形式に正規化

    0b1.111011101 x 2(^6)

  3. 単精度浮動小数点数データ構造に変換

    • 符号部: 0
    • 指数部: 6 + 127 = 134
    • 仮引数部: 111011101

Swiftによる確認

let f = 123.625 as Float
print("符号: ", f.sign)
print("指数: ",  f.exponent)
print("指数部: ", f.exponentBitPattern)
print("仮引数部: ", String(f.significandBitPattern, radix:2));
/*
符号:  plus
指数:  6
指数部:  133
仮引数部:  11101110100000000000000
*/

123_456_789

仮引数部が23桁を超えるので、丸め誤差が発生する

  1. 2進数に変換

    0b111010110111100110100010101

  2. 単精度浮動小数点数形式に正規化

    0b1.11010110111100110100010(101) x 2^(26)

  3. 単精度浮動小数点数データ構造に変換

    • 符号部: 0
    • 指数部: 26 + 127 = 153
    • 仮引数部: 11010110111100110100011 // 桁上り

Swiftによる確認

let f = 123_456_789 as Float
print("符号: ", f.sign)
print("指数: ",  f.exponent)
print("指数部: ", f.exponentBitPattern)
print("仮引数部: ", String(f.significandBitPattern, radix:2));
/*
符号:  plus
指数:  26
指数部:  153
仮引数部:  11010110111100110100011
*/

0.1

仮引数部が23桁を超えるので、丸め誤差が発生する

  1. 2進数に変換

    0b0.0001100110011001101

  2. 単精度浮動小数点数形式に正規化

    0b1.100110011001101 x 2^(-4)

  3. 単精度浮動小数点数データ構造に変換

    • 符号部: 0
    • 指数部: -4 + 127 = 123
    • 仮引数部: 100110011001101

Swiftによる確認

let f = 0.1 as Float
print("符号: ", f.sign)
print("指数: ",  f.exponent)
print("指数部: ", f.exponentBitPattern)
print("仮引数部: ", String(f.significandBitPattern, radix:2));
/*
符号:  plus
指数:  -4
指数部:  123
仮引数部:  10011001100110011001101

*/

単精度浮動小数点数(IEEE754) の作り方

書いたこと

  1. 単精度浮動小数点数形式とデータ構造
  2. 10進数から単精度浮動小数点数データ構造への変換
  3. 精度と誤差

単精度浮動小数点数形式とデータ構造

2進数をそのままでは単精度浮動小数点数データ構造に変換することはできない。 一旦、単精度浮動小数点数形式に正規化を行う必要がある。

単精度浮動小数点数形式と正規化

浮動小数点数データ構造に変換する前段階として、2進数を以下のように最上位ビットを1にして、位取りを2の指数で表すようにする。

f:id:yossan2:20201228232141p:plain
単精度浮動小数点数形式

この形式に変換することを正規化という。

例 3.1415の正規化

f:id:yossan2:20201231145808p:plain
3.1415の正規化

正規化方法

1. 3.1415を2進数に変換する

0b11.00100100001110010101100

2. 最上位ビットが1になるように位を調整する

0b1.10010010000111001010110 x 2^1

単精度浮動小数点数データ構造への変換

正規化した2進数とIEEE754で定義された単精度浮動小数点数データ構造は以下の関係を持つ。

f:id:yossan2:20201228220851p:plain
単精度浮動小数点数(IEEE754)データ構造

符号部

1ビットで構成され、正規化した際に正数のときは0、負数のときは1。

指数部

8ビットで構成され、指数部の値は、正規化した際の指数値の値に+ 7F(127)足した値となる。 これによって0 ~ 7Eまでの値に負数(-127 ~ -1)を割当て、80 ~ FFまでの値に正数(1 ~ 127)を割りてている。

f:id:yossan2:20201228234832p:plain
指数値と指数部の関係

仮引数部

23ビットで構成され、正規化した値に対して小数点以下上位23ビット取得する。

例 3.1415

2進数に変換

0b11.00100100001110010101100

正規化した値

0b1.100100100001110010101100

正規化した値をデータ構造に変換

正規化した値 データ構造に割当てられる値
符号: 正数 符号部: 0
指数: 1 指数部: 128
小数点以下上位24桁 仮引数部
100100100001110010101100 10010010000111001010110

2. 精度と誤差

単精度浮動小数点数は、位取りを整えることが出来るため、値の大小に関わらず常に精度は2進数で小数点以下23桁が保証される

例: 円周率が3.1415として、直径が250と0.25における精度を10進数で考える。(1の位を最上位に正規化)

有効桁数 250 0.25
小数点以下4桁 7.8535 x 102 7.8375 x 10^-1
小数点以下3桁 7.854 x 102 7.854 x 10^-1
小数点以下2桁 7.85 x 102 7.85 x 10^-1
小数点以下1桁 7.9 x 102 7.9 x 10^-1

値の大小によって精度は変わらないが、有効桁数によって、本来の値と誤差が発生している。

代表的な誤差

  • 丸め誤差: 23桁をオーバーフローした時
  • 情報落ち: 巨大な数字と小さな数字を足した時
  • (有効数字の)桁落ち: 極めて近い値同士、引き算した時

丸め誤差 (打切り誤差)

単精度浮動小数点数に変換した際に、小数点数以下23桁を超える範囲は、24桁目で丸められる。 (Swiftの場合、1ビットの数が偶数の場合は、桁上りし、奇数の場合、切り捨てられていると思われる)

例1: 16777215 (0b1.1111111111111111111111) より大きい数

16777215より大きい数は2進数に変換し、正規化すると24桁を超える

f:id:yossan2:20210102000304p:plain

例2: 0.1

0.75は0.5(1/2)と0.25(1/4)の和で表せるが、0.1は表せないため、2進数に変換すると無限小数となり、23桁で打ち切られる。

0.1を2進数に変換

0b0.000110011001100110011001100110

浮動小数点数形式に正規化

0b1.10011001100110011001101 \* 2^(-4)
  • 0b1.10011001100110011001101 = 1.6000000238418579
  • 1.60000002384185791016 \* 2^(-4) = 0.10000000149

したがって、0.1は単精度浮動小数点数では、0.10000000149 となる。

例: Swiftでの確認 0.1を10回足しても1.0にならない

var f = 0.1 as Float
for _ in 0..<10 {
    f += 0.1
}
print(f == 1.0) // false

情報落ち

浮動小数点数の和を計算する際、指数部がそろえられるため、巨大な数字との和をとると、小さい方の値が落ちてしまう。

例: Swiftによる確認 1千万と0.1との和

var f = 10_000_000 as Float
for _ in 0..<1000 {
    f += 0.1
}

print(f == 10_000_000) // true

桁落ち

極めて近い値同士で引き算をすると、有効数字が小さくなり、0埋めされることによって誤差が発生する。

例: sqrt(113) - sqrt(112)

それぞれ2進数で表すと

sqrt(113) =  0b1010.10100001010100010100
sqrt(112) =  0b1010.10010101001111111101

浮動小数点数形式へ正規化

sqrt(113) =  0b1.01010100001010100010100 x 2^(3)
sqrt(112) =  0b1.01010010101001111111101 x 2^(3)

したがって

sqrt(113) - sqrt(112) = 0b0.00000001100000100010111 x 2^(3)

正規化すると0埋めが発生する

sqrt(113) - sqrt(112) = 0b1.10000010001011100000000 x 2^(-5)