6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

numクレートを使って数値計算を複数の型に対して記述する

Last updated at Posted at 2021-12-06

この記事は「OPTIMIND x Acompany Advent Calendar 2021」 6日目の記事です。

こんにちは。オプティマインドの菊池です。
Rustに関する小ネタとして、num クレートについて書きたいと思います。

ジェネリクスを用いたadd関数

皆様は、業務中や趣味のプログラミング、あるいはトイレ、夢の中などで、関数を書くことがしばしばあると思います。

そうした関数漬けの日々を送るあなたは、あるRustによる開発プロジェクトの中で以下のような革命的な関数を世に産み落としました。

fn add(a: u128, b: u128) -> u128 {
    a + b
}

この関数はあまりにも革命的なので、プロジェクトコードのあらゆる箇所で使われるようになったのですが、ある日、ちょっと困ったことが起きました。このadd関数を、usize型の整数に対しても使いたいという要望が出てきたのです。

最も簡単な解決策は、下記のようにusize用の別の関数をもう一つ作ってしまうことです。

fn add_usize(a: usize, b: usize) -> usize {
    a + b
}

これで足元の課題は解決しましたが、今後u64でも使いたい、i128でも使いたいなどといった要望が出てくるたびに関数を作っていたのではとても面倒です。できれば、何度も実質的に同じ振る舞いを記述せずに済ませたいところです。

幸い、Rustはジェネリクスの機能を持っているため、std::ops::Addトレイトを実装した型というジェネリック境界により、以下のように汎用的に記述することができます。

fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

これで、std::ops::Addを実装している型であれば、どんな型でもadd関数を使うことができます。

ceil関数の場合は?

同じことを、以下のceil関数でも試してみたいと思います。(これはa/bの整数切り上げを計算する関数です。こちらなどを参照。)

fn ceil(a: u128, b: u128) -> u128 {
    (a + b - 1) / b
}

この関数の中では、加算、減算、除算が使われているので、各演算に対応したトレイトstd::ops::Addstd::ops::Substd::ops::Divと、2度使っているbのためにCopyトレイトを実装してやればよさそうです。

それを実装したのが以下です。

fn ceil<
    T: Copy + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Div<Output = T>,
>(
    a: T,
    b: T,
) -> T {
    (a + b - 1) / b
}

しかし、このコードをコンパイルしてみると、以下のようなエラーが起こってしまいました。

| (a + b - 1) / b
| _______^ expected type parameter T, found integer
|
= note: expected type parameter T
found type {integer}

コード中に登場する整数1について、コード中に具体的な型を特定する手段がなく何らかの整数型としか類推できないため、コンパイルエラーとなってしまいます。このように、処理の中に実際の数値が登場する場合は、addと同じ方法では記述できません。これは困りました。

numクレートでceil関数を実装

このようなケースで役に立つのが、numクレートです。numクレートはcargo.tomlに以下のように記述することで利用することができます。

[dependencies]
num="0.4.0"

このクレートを使うと、先ほどの1の部分はT::one()と書くことができ、ceil関数は以下のようになります。

fn ceil<T: Copy + num::Integer>(a: T, b: T) -> T {
    (a + b - T::one()) / b
}

1の部分だけでなく、トレイト境界の記述も非常にスッキリしています。numクレートはこのように、数値の計算に関して直感的に利用しやすいトレイトを提供しています。

numクレートに実装されている値

numクレートには数値として、Zeroトレイトと、Oneトレイトが用意されています。それぞれ::zero()``::one()で、各型の0、1の値を呼び出すことができ、これらはnum::Numnum::Integerなどに実装されています。

fn do_something<T: num::Num>() {
    let zero = T::zero();
    let one = T::one();
}

2、3などの数字を扱いたい場合も、以下のようにして実現することができます。

fn do_something<T: Copy + num::Num>() {
    let one: T = T::one();
    let two: T = one + one;
    let three: T = one + two;
}

numクレートに含まれている便利なトレイト・構造体

最後に、numクレートに含まれているトレイト・構造体で、便利なものをまとめておきます。

num::Num

整数型、浮動小数点数型を含む数値全般を扱うトレイト。加減乗除と剰余の演算、PartialEq、Zero、Oneが実装されている。

num:Integer

整数型を扱うトレイト。num:Numの実装されているトレイトに加え、Eq、Ordなどが実装される。

is_evenis_oddgcdlcmextended_gcdなどのちょっとしたメソッドも実装してくれる。

num::Float

浮動小数点数型を扱うトレイト。num:Numの実装されているトレイトに加え、Negなどが実装される。

num::BigInt

巨大な整数型を扱うことができる型。

let mut x: BigInt = 1.into();
for i in 2..50 {
    x *= i;
}
println!("{:?}", x); // 608281864034267560872252163321295376887552831379210240000000000

num::Complex

複素数を扱うことができる型。

let mut x: Complex<i128> = Complex::new(10, 10);
let mut y: Complex<i128> = Complex::new(2, 5);
println!("{}", (x * y));        // -30+70i
println!("{}", (x * y).conj()); // -30-70i

最後に宣伝

株式会社オプティマインドでは、一緒に働く仲間を大募集中です。
カジュアル面談も大歓迎ですので、気軽にお声がけください。

【エンジニア領域の募集職種】
ソフトウェアエンジニア
QAエンジニア
Androidアプリエンジニア
組合せ最適化アルゴリズムエンジニア
経路探索アルゴリズムエンジニア
バックエンドエンジニア
インフラエンジニア
UXUIデザイナー

【ビジネス領域の募集職種】
セールスコンサルタント
採用・人事

『オプティマインドってどんな会社?』については、こちらから
Wantedlyでもこちらで募集中

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?