Help us understand the problem. What is going on with this article?

Rust 1.39.0でabsがconst fnになる

Rust 1.39.0でabsがconst fn対応する

RustのリポジトリのRELEASES.mdを見ていて気づいたのですが、どうやらabsconst fnになるようです。

つまりこれが通る

fn main() {
    const A: i32 = 42_i32.abs();
    const B: i32 = (-42_i32).abs();
    assert_eq!(A, B);
}

実行結果

過去の自分の記事で制限が多すぎてabsすら素直に書けないと言っていたので標準入するのは嬉しいです。

実装を見てみる

libcore/num/mod.rs#L1962-L1981が整数のabsの実装っぽいので見てみましょう。

pub const fn abs(self) -> Self {
    // Note that the #[inline] above means that the overflow
    // semantics of the subtraction depend on the crate we're being
    // inlined into.

    // sign is -1 (all ones) for negative numbers, 0 otherwise.
    let sign = self >> ($BITS - 1);
    // For positive self, sign == 0 so the expression is simply
    // (self ^ 0) - 0 == self == abs(self).
    //
    // For negative self, self ^ sign == self ^ all_ones.
    // But all_ones ^ self == all_ones - self == -1 - self.
    // So for negative numbers, (self ^ sign) - sign is
    // (-1 - self) - -1 == -self == abs(self).
    //
    // The subtraction overflows when self is min_value(), because
    // (-1 - min_value()) - -1 is max_value() - -1 which overflows.
    // This is exactly when we want self.abs() to overflow.
    (self ^ sign) - sign
}

ビット演算してそうだけどわからん。
$BITSってなんだ。

もう少し範囲を広げて見てみると、どうやらabsの実装はint_implというマクロの定義で囲まれており、$BITSというのは整数型のビット数のようです。

// `Int` + `SignedInt` implemented for signed integers
macro_rules! int_impl {
    ($SelfT:ty, $ActualT:ident, $UnsignedT:ty, $BITS:expr, $Min:expr, $Max:expr, $Feature:expr,
     $EndFeature:expr, $rot:expr, $rot_op:expr, $rot_result:expr, $swap_op:expr, $swapped:expr,
     $reversed:expr, $le_bytes:expr, $be_bytes:expr,
     $to_xe_bytes_doc:expr, $from_xe_bytes_doc:expr) => {

int_implの先頭

#[lang = "i32"]
impl i32 {
    int_impl! { i32, i32, u32, 32, -2147483648, 2147483647, "", "", 8, "0x10000b3", "0xb301",
        "0x12345678", "0x78563412", "0x1e6a2c48", "[0x78, 0x56, 0x34, 0x12]",
        "[0x12, 0x34, 0x56, 0x78]", "", "" }
}

int_implの使用箇所(i32)

absの実装 (i32型の場合)

$BITSは整数型のビット数ということがわかったのでi32型の場合のabsの実装は下記のようになります。
(コメントは消してます)

pub const fn abs(self) -> Self {
    let sign = self >> (32 - 1);
    (self ^ sign) - sign
}

順番に確認していきましょう。


最初の行では自身の値を31ビット右シフトして、先頭のビットだけをsignに代入しています。

let sign = self >> (32 - 1);

Rustの符号あり数値は「2の補数」という体系が使用されています。
そのためsignの値はselfが負の数であれば-1、それ以外なら0になります。
(最初に貼ったコードのコメントにsign is -1 (all ones) for negative numbers, 0 otherwise.とありますね)


次の行ではselfsignのXORからsignを引いています。

(self ^ sign) - sign
self >= 0のとき

self0以上の場合sign0なので

(self ^ 0) - 0

となります。
self ^ 0の値はselfのまま。そこから0を引いてもselfの値は変わらないのでselfがそのまま返ります。

self < 0(負の数)のとき

selfが負の数のときはsign-1なので

(self ^ -1) - (-1)

-1を「2の補数」で表すと11111111111111111111111111111111なのでself ^ -1selfのビットがすべて反転します。
そこからsign(-1)を引いているので、これは1を足しています。
すべてのビットを反転させた後に1を足すという処理は「2の補数」では符号を反転させる処理なので、selfの符号が反転した値が返ります。

所感

標準のabsconst fnになった。
その実装はビット演算。

初めてlibcoreのコードを読みました。

いつかC++のconstexprのようにconst fnも制限緩和されて楽に書けるようになるといいですね。

→だいぶ楽に書けるようになりました
Rustのconst fnの制限がさらに緩和された(Rust 1.46.0)

block
Game/Game Programming/C++/Rust/C#
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away