Rust 1.39.0でabsがconst fn
対応する
RustのリポジトリのRELEASES.mdを見ていて気づいたのですが、どうやらabs
がconst 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) => {
#[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]", "", "" }
}
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.
とありますね)
次の行ではself
とsign
のXORからsign
を引いています。
(self ^ sign) - sign
self >= 0
のとき
self
が0
以上の場合sign
は0
なので
(self ^ 0) - 0
となります。
self ^ 0
の値はself
のまま。そこから0
を引いてもself
の値は変わらないのでself
がそのまま返ります。
self < 0
(負の数)のとき
self
が負の数のときはsign
は-1
なので
(self ^ -1) - (-1)
-1
を「2の補数」で表すと11111111111111111111111111111111
なのでself ^ -1
はself
のビットがすべて反転します。
そこからsign
(-1
)を引いているので、これは1
を足しています。
すべてのビットを反転させた後に1
を足すという処理は「2の補数」では符号を反転させる処理なので、self
の符号が反転した値が返ります。
所感
標準のabs
がconst fn
になった。
その実装はビット演算。
初めてlibcoreのコードを読みました。
いつかC++のconstexpr
のようにconst fn
も制限緩和されて楽に書けるようになるといいですね。
→だいぶ楽に書けるようになりました
Rustのconst fnの制限がさらに緩和された(Rust 1.46.0)