Chapter 03

数値を扱うTrait (num-traits crate)

Rustで数値型を扱う際に有用なTraitについてまとめていきます。

rust-num organization 以下に数値関係のcrateがまとまっています。num crateは全体をまとめて再エクスポートしたもので、実体としては num-traitsnum-complex 等のそれぞれのcrateで開発されています。

前提知識1: std::ops::Add

Rustでは+等の演算子のオーバーロードもTraitとして実装されます:

use std::ops::Add;

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

このようにT型にAddを要求しておくと+演算子で足し算が出来るようになります。

pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

Add Traitはこのように足される Rhs と足された結果 Output をそれぞれ型パラメータと関連型として持っています。Rhs毎にAdd<Rhs> Traitが定義され、このTraitに対してOutput型が1つ決まります。

前提知識2: Traitの継承

Traitの定義時に他のTraitを継承できる。

trait A {
  fn fa(&self);
}
trait B: A {
  fn fb(&self);
}

fn f<T: B>(a: &T) {
  a.fa();  // Aの関数も使える
  a.fb();
} 

この準備の下でnum_traits crateを見ていきます。

num_traits::Num

num_traits::NumOps演算子だけ定義されいてるTraitです:

use std::ops::*;

pub trait NumOps<Rhs = Self, Output = Self>:
   Add<Rhs, Output = Output>
 + Sub<Rhs, Output = Output>
 + Mul<Rhs, Output = Output>
 + Div<Rhs, Output = Output>
 + Rem<Rhs, Output = Output>
{}

これで足し算・掛け算他が入ったので、単位元を定義できます:

use std::ops::*;

pub trait Zero: Sized + Add<Self, Output = Self> {
    fn zero() -> Self;
    fn is_zero(&self) -> bool;
    fn set_zero(&mut self);
}

pub trait One: Sized + Mul<Self, Output = Self> {
    fn one() -> Self;
    fn is_one(&self) -> bool where Self: PartialEq;
    fn set_one(&mut self);
}

これらに加えてさらにPartialEqを追加したものがNumです。

use num_traits::{Zero, One, NumOps};

pub trait Num: PartialEq + Zero + One + NumOps {
  // 略
}

演算子には+=のように自己に代入するものもありnum_traits::NumOpsAssignが対応します。こちらは出力がSelfで固定になるのでOutputはパラメータに存在しません。

use std::ops::*;

pub trait NumAssignOps<Rhs = Self>:
   AddAssign<Rhs>
 + SubAssign<Rhs>
 + MulAssign<Rhs>
 + DivAssign<Rhs>
 + RemAssign<Rhs>
{}

これを追加したのが NumAssign です:

use num_traits::{Num, NumAssignOps};

pub trait NumAssign: Num + NumAssignOps {}

これらのTraitは全て usize, isize, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64等の標準の数値型に対して実装されています。

num_traits::NumCast

プリミティブ型に変換するためのTraitがToPrimitiveです。

pub trait ToPrimitive {
    fn to_i64(&self) -> Option<i64>;
    fn to_u64(&self) -> Option<u64>;
    fn to_isize(&self) -> Option<isize>;
    fn to_i8(&self) -> Option<i8>;
    fn to_i16(&self) -> Option<i16>;
    fn to_i32(&self) -> Option<i32>;
    fn to_i128(&self) -> Option<i128>;
    fn to_usize(&self) -> Option<usize>;
    fn to_u8(&self) -> Option<u8>;
    fn to_u16(&self) -> Option<u16>;
    fn to_u32(&self) -> Option<u32>;
    fn to_u128(&self) -> Option<u128>;
    fn to_f32(&self) -> Option<f32>;
    fn to_f64(&self) -> Option<f64>;
}

Optionになっているのは、値がその型で表現できない場合があるからです。逆向きがFromPrimitiveです。

pub trait FromPrimitive: Sized {
    fn from_i64(n: i64) -> Option<Self>;
    fn from_u64(n: u64) -> Option<Self>;
    fn from_isize(n: isize) -> Option<Self>;
    fn from_i8(n: i8) -> Option<Self>;
    fn from_i16(n: i16) -> Option<Self>;
    fn from_i32(n: i32) -> Option<Self>;
    fn from_i128(n: i128) -> Option<Self>;
    fn from_usize(n: usize) -> Option<Self>;
    fn from_u8(n: u8) -> Option<Self>;
    fn from_u16(n: u16) -> Option<Self>;
    fn from_u32(n: u32) -> Option<Self>;
    fn from_u128(n: u128) -> Option<Self>;
    fn from_f32(n: f32) -> Option<Self>;
    fn from_f64(n: f64) -> Option<Self>;
}

これを使って統一的に扱えるようにしたのがNumCastです:

use num_traits::ToPrimitive;
pub trait NumCast: Sized + ToPrimitive {
    fn from<T: ToPrimitive>(n: T) -> Option<Self>;
}

num_traits::PrimInt

整数型の性質を抽象化したTraitです。ビット演算が多く実装されています。

use std::ops::*;
use num_traits::*;

pub trait PrimInt:
   Sized + Copy + Num + NumCast + Bounded + PartialOrd + Ord + Eq + Not<Output = Self>
 + BitAnd<Output = Self> + BitOr<Output = Self> + BitXor<Output = Self>
 + Shl<usize, Output = Self> + Shr<usize, Output = Self>
 + CheckedAdd<Output = Self> + CheckedSub<Output = Self> + CheckedMul<Output = Self> + CheckedDiv<Output = Self>
 + Saturating
{
    fn count_ones(self) -> u32;
    fn count_zeros(self) -> u32;
    fn leading_zeros(self) -> u32;
    fn trailing_zeros(self) -> u32;
    fn rotate_left(self, n: u32) -> Self;
    fn rotate_right(self, n: u32) -> Self;
    fn signed_shl(self, n: u32) -> Self;
    fn signed_shr(self, n: u32) -> Self;
    fn unsigned_shl(self, n: u32) -> Self;
    fn unsigned_shr(self, n: u32) -> Self;
    fn swap_bytes(self) -> Self;
    fn from_be(x: Self) -> Self;
    fn from_le(x: Self) -> Self;
    fn to_be(self) -> Self;
    fn to_le(self) -> Self;
    fn pow(self, exp: u32) -> Self;
}

num_traits::Float

続いて浮動小数点に特化したTraitです。標準的な数学関数が定義されています。

use std::{num::FpCategory, ops::Neg};
use num_traits::{Num, NumCast};

pub trait Float: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
    fn nan() -> Self;
    fn infinity() -> Self;
    fn neg_infinity() -> Self;
    fn neg_zero() -> Self;
    fn min_value() -> Self;
    fn min_positive_value() -> Self;
    fn max_value() -> Self;
    fn is_nan(self) -> bool;
    fn is_infinite(self) -> bool;
    fn is_finite(self) -> bool;
    fn is_normal(self) -> bool;
    fn classify(self) -> FpCategory;
    fn floor(self) -> Self;
    fn ceil(self) -> Self;
    fn round(self) -> Self;
    fn trunc(self) -> Self;
    fn fract(self) -> Self;
    fn abs(self) -> Self;
    fn signum(self) -> Self;
    fn is_sign_positive(self) -> bool;
    fn is_sign_negative(self) -> bool;
    fn mul_add(self, a: Self, b: Self) -> Self;
    fn recip(self) -> Self;
    fn powi(self, n: i32) -> Self;
    fn powf(self, n: Self) -> Self;
    fn sqrt(self) -> Self;
    fn exp(self) -> Self;
    fn exp2(self) -> Self;
    fn ln(self) -> Self;
    fn log(self, base: Self) -> Self;
    fn log2(self) -> Self;
    fn log10(self) -> Self;
    fn max(self, other: Self) -> Self;
    fn min(self, other: Self) -> Self;
    fn abs_sub(self, other: Self) -> Self;
    fn cbrt(self) -> Self;
    fn hypot(self, other: Self) -> Self;
    fn sin(self) -> Self;
    fn cos(self) -> Self;
    fn tan(self) -> Self;
    fn asin(self) -> Self;
    fn acos(self) -> Self;
    fn atan(self) -> Self;
    fn atan2(self, other: Self) -> Self;
    fn sin_cos(self) -> (Self, Self);
    fn exp_m1(self) -> Self;
    fn ln_1p(self) -> Self;
    fn sinh(self) -> Self;
    fn cosh(self) -> Self;
    fn tanh(self) -> Self;
    fn asinh(self) -> Self;
    fn acosh(self) -> Self;
    fn atanh(self) -> Self;
    fn integer_decode(self) -> (u64, i16, i8);
    fn epsilon() -> Self;
    fn to_degrees(self) -> Self;
    fn to_radians(self) -> Self;
}

num_complex::Complex

複素数は構造体 num_complex::Complex<T> で定義されており、関連関数として数学関数が定義されています。0.3.0でいくつか破壊的な変更が入っているので注意です。

[dependencies]
num_complex = "0.3.1"
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Default)]
#[repr(C)]
pub struct Complex<T> {
    /// Real portion of the complex number
    pub re: T,
    /// Imaginary portion of the complex number
    pub im: T,
}
pub type Complex32 = Complex<f32>;
pub type Complex64 = Complex<f64>;

これは repr(C) で定義されいているのでC99の _Complex 型、C++の std::complex<T> と同じメモリ配置になります。

これを複素数と実数型を合わせて同じように使えるようにしたTraitが cauchy::Scalar です。

use std::{
  fmt::{Display, Debug, LowerExp, UpperExp},
  iter::{Sum, Product},
  ops::Neg,
};
use num_traits::*;
use serde::{Serialize, Deserialize};
use rand::Rng;

pub trait Scalar:
    NumAssign + FromPrimitive + NumCast + Neg<Output = Self>
  + Copy + Clone
  + Display + Debug + LowerExp + UpperExp
  + Sum + Product + Serialize + for<'de> Deserialize<'de> + 'static
{
    type Real: Scalar<Real = Self::Real, Complex = Self::Complex> + NumOps<Self::Real, Self::Real> + Float;
    type Complex: Scalar<Real = Self::Real, Complex = Self::Complex> + NumOps<Self::Real, Self::Complex> + NumOps<Self::Complex, Self::Complex>;
    fn real<T: ToPrimitive>(re: T) -> Self::Real;
    fn complex<T: ToPrimitive>(re: T, im: T) -> Self::Complex;
    fn from_real(re: Self::Real) -> Self;
    fn add_real(self, re: Self::Real) -> Self;
    fn sub_real(self, re: Self::Real) -> Self;
    fn mul_real(self, re: Self::Real) -> Self;
    fn div_real(self, re: Self::Real) -> Self;
    fn add_complex(self, im: Self::Complex) -> Self::Complex;
    fn sub_complex(self, im: Self::Complex) -> Self::Complex;
    fn mul_complex(self, im: Self::Complex) -> Self::Complex;
    fn div_complex(self, im: Self::Complex) -> Self::Complex;
    fn pow(&self, n: Self) -> Self;
    fn powi(&self, n: i32) -> Self;
    fn powf(&self, n: Self::Real) -> Self;
    fn powc(&self, n: Self::Complex) -> Self::Complex;
    fn re(&self) -> Self::Real;
    fn im(&self) -> Self::Real;
    fn as_c(&self) -> Self::Complex;
    fn conj(&self) -> Self;
    fn abs(&self) -> Self::Real;
    fn square(&self) -> Self::Real;
    fn sqrt(&self) -> Self;
    fn exp(&self) -> Self;
    fn ln(&self) -> Self;
    fn sin(&self) -> Self;
    fn cos(&self) -> Self;
    fn tan(&self) -> Self;
    fn asin(&self) -> Self;
    fn acos(&self) -> Self;
    fn atan(&self) -> Self;
    fn sinh(&self) -> Self;
    fn cosh(&self) -> Self;
    fn tanh(&self) -> Self;
    fn asinh(&self) -> Self;
    fn acosh(&self) -> Self;
    fn atanh(&self) -> Self;
    fn rand(rng: &mut impl Rng) -> Self;
}