Mojo 入門

第3章

数値型と SIMD 入門

Mojo の整数・浮動小数点数・Bool の性質を、Python との違いに注目して学びます。オーバーフロー、型の変換、math モジュール、そして1回の命令で複数の値を計算する SIMD の入り口までを扱います。

この章の内容
  1. 3.1Int は 64 ビット固定の整数
  2. 3.2符号付き・符号なしの整数型
  3. 3.3浮動小数点数と Bool
  4. 3.4数値リテラルの書き方
  5. 3.5算術演算子
  6. 3.6型を変換する
  7. 3.7math モジュール
  8. 3.8SIMD 入門:複数の値を1度に計算する
  9. 3.9Python との違いの早見表
  10. 3.10まとめ

Python の int は、どれだけ大きくなってもあふれません。ところが Mojo の整数は、サイズが決まっています。この違いは、数値を扱うあらゆる場面に影響します。この章では、Mojo の数値の型を一通り見て、Python の感覚のままだとどこでつまずくのかを、実際に動かして確かめます。

この章を終えると、次のことができるようになります。

文字列は第4章、性能の測り方は第19章と第20章で扱います。この章では「正しく動く」ことに集中します。

3.1Int は 64 ビット固定の整数

まず、Python と同じつもりで、とても大きな整数を作ってみましょう。

int_limits.mojo
def main():
    print(Int.MAX)
    print(Int.MIN)
    print(Int.MAX + 1)
Ubuntu
~$ mojo run int_limits.mojo
9223372036854775807
-9223372036854775808
-9223372036854775808

Int.MAX と Int.MIN は、Int が表せる最大値と最小値です。最大値は 9223372036854775807、つまり 2 の 63 乗から 1 を引いた数です。Int は 64 ビットの符号付き整数 で、Python の int のように大きさを自動で増やしてはくれません。

3行目に注目してください。最大値に 1 を足したのに、エラーにはならず、結果は最小値に変わりました。表せる範囲を超えると、反対側の端に ぐるっと回る(ラップアラウンドする)のです。

もっと身近な計算でも起きます。3037000500 の2乗は、9.2 × 10^18 を少し超える数で、Int の最大値より大きくなります。

int_square.mojo
def main():
    var x: Int = 3037000500
    print(x * x)
    var base: Int = 2
    print(base ** 62)
    print(base ** 63)
    print(base ** 64)
Ubuntu
~$ mojo run int_square.mojo
-9223372036709301616
4611686018427387904
-9223372036854775808
0

3037000500 の2乗は本当は 9223372037000250000 で、最大値より少し大きい数です。表示されたのは負の数でした。2 ** 62 までは正しく計算できていますが、2 ** 63 は最小値になり、2 ** 64 は 0 になりました。

同じ計算を Python でやると、結果は次のとおりです。

3.2符号付き・符号なしの整数型

Int のほかにも、ビット数を決めた整数型があります。

型ビット数符号
Int8 Int16 Int32 Int648 / 16 / 32 / 64あり
UInt8 UInt16 UInt32 UInt648 / 16 / 32 / 64なし
Int64(この環境では)あり

範囲は、型の MAX と MIN で確かめられます。サイズ(バイト数)は size_of で分かります。

sized_ints.mojo
from std.sys import size_of

def main():
    print("Int8  ", Int8.MIN, Int8.MAX)
    print("Int16 ", Int16.MIN, Int16.MAX)
    print("Int32 ", Int32.MIN, Int32.MAX)
    print("Int64 ", Int64.MIN, Int64.MAX)
    print("UInt8 ", UInt8.MIN, UInt8.MAX)
    print("UInt16", UInt16.MIN, UInt16.MAX)
    print("UInt32", UInt32.MIN, UInt32.MAX)
    print("UInt64", UInt64.MIN, UInt64.MAX)
    print(size_of[Int8](), size_of[Int32](), size_of[Int]())
Ubuntu
~$ mojo run sized_ints.mojo
Int8   -128 127
Int16  -32768 32767
Int32  -2147483648 2147483647
Int64  -9223372036854775808 9223372036854775807
UInt8  0 255
UInt16 0 65535
UInt32 0 4294967295
UInt64 0 18446744073709551615
1 4 8

size_of は std.sys にあるので、from std.sys import size_of で取り込みます。size_of[Int32]() の [ ] は、第12章で学ぶ「コンパイル時パラメータ」です。ここでは「型を渡している」と読んでおけば十分です。最後の行は、Int8 が 1 バイト、Int32 が 4 バイト、Int が 8 バイトであることを表します。

符号なしの型は、負の数を表せないかわりに、正の側の範囲が2倍あります。UInt8 は 0〜255 で、画像のピクセルの明るさなどによく使われます。

範囲を超えたときの動き

サイズの小さい型では、あふれやすくなります。

wrap.mojo
def main():
    var a: Int8 = 127
    a += 1
    print(a)

    var b: UInt8 = 0
    b -= 1
    print(b)

    var c: UInt64 = UInt64.MAX
    c += 1
    print(c)

    var big: Int = 1000
    print(Int8(big), UInt8(300))
Ubuntu
~$ mojo run wrap.mojo
-128
255
0
-24 44

どれも Int と同じく、エラーにならずにぐるっと回ります。Int8 の 127 に 1 を足すと -128、UInt8 の 0 から 1 を引くと 255 です。大きな値を小さい型に変換した最後の行では、上位のビットが切り捨てられます。1000 を Int8 にすると -24、300 を UInt8 にすると 44 になりました。

3.3浮動小数点数と Bool

小数は Float64(64ビット)と Float32(32ビット)で表します。ふつうは Float64 を使います。Python の float と同じ形式です。

floats.mojo
def main():
    var a: Float64 = 0.1
    var b: Float64 = 0.2
    print(a + b)
    print(a + b == 0.3)

    var c = Float32(0.1) + Float32(0.2)
    print(c)
    print(Float64(c))
    print(Float64(Float32(0.1)))
Ubuntu
~$ mojo run floats.mojo
0.30000000000000004
False
0.3
0.30000001192092896
0.10000000149011612

0.1 + 0.2 は 0.30000000000000004 になり、0.3 とは等しくありません。2進数では 0.1 を正確に表せないための誤差で、Python でもまったく同じ結果になります。

Float32 は桁数が少ないので、表示は短くなります。Float32(0.1) + Float32(0.2) は 0.3 と表示されます。けれども、それを Float64 に変えてみると 0.30000001192092896 で、実際には 0.3 から少しずれた値です。Float32(0.1) を Float64 で見ると 0.10000000149011612 です。表示が短いのは、Float32 の精度の範囲で最も短く書ける形を選んでいるからで、誤差がないわけではありません。

無限大と NaN

special.mojo
from std.math import isnan, isinf
from std.utils.numerics import max_finite

def main():
    var zero: Float64 = 0
    print(Float64(1) / zero)
    var n = zero / zero
    print(n, n == n, isnan(n))
    print(isinf(Float64(1) / zero))
    print(Float64.MAX)
    print(max_finite[DType.float64]())
Ubuntu
~$ mojo run special.mojo
inf
nan False True
True
inf
1.7976931348623157e+308

0 で割っても、Python のように ZeroDivisionError にはなりません。1 / 0 は無限大(inf)、0 / 0 は NaN(数でないもの)になります。NaN は自分自身とも等しくないので、n == n は False です。NaN の判定には isnan を使います。

Float64.MAX は、有限の最大値ではなく inf でした。有限の最大値が欲しいときは max_finite[DType.float64]() を使います。DType.float64 は、あとの SIMD の節で出てくる「要素の種類」を表す値です。

Bool

Bool は True と False を表す型です。

bools.mojo
def main():
    var flag = True
    print(flag, not flag)
    print(flag and False, flag or False)
    print(Bool(0), Bool(5), Bool(0.0), Bool(1.5))
Ubuntu
~$ mojo run bools.mojo
True False
False True
False True False True

論理演算は Python と同じ and・or・not です。数を Bool(...) に渡すと、0 は False、それ以外は True になります。ただし Python と違って、True + True のように Bool どうしを足すことはできません。Bool は整数のなかまではなく、独立した型です。

3.4数値リテラルの書き方

数をプログラムの中に直接書いたものを リテラル と呼びます。

literals.mojo
def main():
    print(1_000_000)
    print(0xFF, 0b101, 0o17)
    print(1e3, 1.5e-3)
    print(3, 3.0)
Ubuntu
~$ mojo run literals.mojo
1000000
255 5 15
1000.0 0.0015
3 3.0

ここまでは Python と同じです。違いは、リテラルの型が 使われる場所で決まる ことです。var x: Int8 = 5 と書けば 5 は Int8 になり、var y: Float32 = 5 と書けば Float32 になります。何も指定しなければ、整数は Int、小数は Float64 になります。

3.5算術演算子

+、-、*、/、//、%、** と、ビット演算の &、|、^、~、<<、>> があります。割り算の仲間は、負の数が入ると結果が言語によって変わるので、実際に確かめましょう。

ops.mojo
def main():
    var a: Int = -7
    var b: Int = 2
    print(a / b, a // b, a % b)
    print(Float64(a) / Float64(b))
    print(7 // -2, 7 % -2)
    var ten: Int = 10
    print(ten / 4, ten // 4)
    print(-10.5 // 3, 7.5 % 2)
    print(2 ** 10, 2.0 ** -1)
    print(5 >> 1, 5 << 3, 5 & 3, 5 | 3, 5 ^ 3, ~5)
Ubuntu
~$ mojo run ops.mojo
-3 -4 1
-3.5
-4 -1
2 2
-4.0 1.5
1024 0.5
2 40 1 7 6 -6

同じ計算を Python でやってみます。

ops.py
a = -7
b = 2
print(a / b, a // b, a % b)
print(a / b)
print(7 // -2, 7 % -2)
ten = 10
print(ten / 4, ten // 4)
print(-10.5 // 3, 7.5 % 2)
print(2 ** 10, 2.0 ** -1)
print(5 >> 1, 5 << 3, 5 & 3, 5 | 3, 5 ^ 3, ~5)
Ubuntu
~$ python3 ops.py
-3.5 -4 1
-3.5
-4 -1
2.5 2
-4.0 1.5
1024 0.5
2 40 1 7 6 -6

出力を比べてみましょう。違うのは / だけで、ほかは同じです。

// と % の約束は Python とそろっていますが、/ だけは違う、と覚えておきましょう。

** は累乗です。1節で見たように、整数の累乗もあふれます。小数の ** は、2.0 ** -1 のように負の指数も扱えます。

3.6型を変換する

Python では 1 + 2.5 のように、整数と小数を混ぜて計算できました。Mojo では、変数の型が違うと自動では変換されません。

暗黙の変換は拒否される

まず、Int の変数を Int8 の変数に入れようとしてみます。

narrow.mojo
def main():
    var n: Int = 300
    var small: Int8 = n
    print(small)
Ubuntu
~$ mojo run narrow.mojo
/home/user/narrow.mojo:3:23: error: cannot implicitly convert 'Int' value to 'Int8'
    var small: Int8 = n
                      ^
mojo: error: failed to parse the provided Mojo source module

cannot implicitly convert 'Int' value to 'Int8'、つまり「Int の値を Int8 に暗黙には変換できない」というエラーです。Int8 に入りきらないかもしれない値を、黙って切り捨てるのを防いでくれています。

Int と Float64 の間も同じです。

int_float.mojo
def main():
    var a: Int = 3
    var f: Float64 = a
    print(f)
    var g: Float64 = 2.5
    var k: Int = g
    print(k)
Ubuntu
~$ mojo run int_float.mojo
/home/user/int_float.mojo:3:22: error: cannot implicitly convert 'Int' value to 'Float64'
    var f: Float64 = a
                     ^
/home/user/int_float.mojo:6:18: error: cannot implicitly convert 'Float64' value to 'Int'
    var k: Int = g
                 ^
mojo: error: failed to parse the provided Mojo source module

どちらの向きも拒否されます。エラーは、問題のある行ごとに報告されました。

計算の中で型を混ぜたときも、エラーになります。

mixed.mojo
def main():
    var a: Int = 3
    var b: Float64 = 2.5
    print(a + b)
    var c: Int8 = 1
    var d: Int16 = 2
    print(c + d)
Ubuntu
~$ mojo run mixed.mojo
/home/user/mixed.mojo:4:15: error: invalid call to '__add__': value passed to 'rhs' cannot be converted from 'Float64' to 'Int'
    print(a + b)
          ~~~~^
Mojo/stdlib/std/simd.mojo:1098:1: note: function declared here
def __add__(self, rhs: Self) -> Self    # note - synthetic signature
^
/home/user/mixed.mojo:7:15: error: invalid call to '__add__': value passed to 'rhs' cannot be converted from 'Int16' to 'Int8'
    print(c + d)
          ~~~~^
Mojo/stdlib/std/simd.mojo:1098:1: note: function declared here
def __add__(self, rhs: Self) -> Self    # note - synthetic signature
^
mojo: error: failed to parse the provided Mojo source module

__add__ は足し算を担当するメソッドです(第13章で学びます)。「引数 rhs を Float64 から Int に変換できない」というメッセージは、Int + Float64 の足し算が定義されていない、という意味です。Int8 と Int16 のような、整数どうしでも同じです。

明示的に変換する

変換したいときは、型の名前を関数のように使って、自分で書きます。

convert.mojo
def main():
    var a: Int = 3
    var g: Float64 = 3.9
    print(Float64(a) + 2.5)
    print(Int(g))
    var neg: Float64 = -3.9
    print(Int(neg))
    var n: Int = 300
    print(Int8(n), Int16(n), UInt8(n))
    print(Int(Int8(5)) + n)
Ubuntu
~$ mojo run convert.mojo
5.5
3
-3
44 300 44
305

3.7math モジュール

平方根や切り上げなどは、標準ライブラリの std.math にあります。Python の import math に対応しますが、Mojo では from std.math import ... と、名前を指定して取り込みます。

mathdemo.mojo
from std.math import sqrt, floor, ceil, trunc, pi

def main():
    print(sqrt(2.0))
    print(floor(-2.5), ceil(2.1), trunc(-2.7))
    print(pi)
    print(round(2.5), round(3.5))
    print(abs(-3), abs(-2.5), min(3, 4), max(3.0, 1.0))
    print(sqrt(Float32(2.0)))
Ubuntu
~$ mojo run mathdemo.mojo
1.4142135623730951
-3.0 3.0 -2.0
3.141592653589793
2.0 4.0
3 2.5 3 3.0
1.4142135

3.8SIMD 入門:複数の値を1度に計算する

ここまでの数値は、1つの値でした。Mojo には、同じ種類の値を複数まとめて持ち、1回の命令で計算できる型 があります。それが SIMD です。SIMD は Single Instruction, Multiple Data(1つの命令で複数のデータ)の略です。

CPU には、たとえば Float32 を4個ならべて、4個ぶんの足し算を1回でこなす命令があります。SIMD 型は、その仕組みを直接使うための型です。

SIMD 値を作る

simd_basic.mojo
def main():
    var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
    var w = SIMD[DType.float32, 4](10.0, 20.0, 30.0, 40.0)
    print(v)
    print(v + w)
    print(v * w)
    print(v * 2)
    print(w / v)
    print(v[0], v[3])
Ubuntu
~$ mojo run simd_basic.mojo
[1.0, 2.0, 3.0, 4.0]
[11.0, 22.0, 33.0, 44.0]
[10.0, 40.0, 90.0, 160.0]
[2.0, 4.0, 6.0, 8.0]
[10.0, 10.0, 10.0, 10.0]
1.0 4.0

SIMD[DType.float32, 4] は、「要素の型が float32 で、要素が4個」という型です。[ ] の中の2つの値は、型の パラメータ です(第12章で詳しく学びます)。1つ目の DType.float32 が要素の種類、2つ目の 4 が要素の個数(レーン と呼びます)を表します。

v + w は、4個の足し算を一度に行います。

スカラーの繰り返しでは4回、SIMD では1回で4つの足し算を行う スカラー:1個ずつ、4回 c[i] = a[i] + b[i] をくり返す 1回目 1+10 11 2回目 2+20 22 3回目 3+30 33 4回目 4+40 44 足し算の命令を4回実行する SIMD:4個まとめて、1回 c = a + b(a と b は 4 レーンの SIMD) a 1 2 3 4 b 10 20 30 40 +(1回の命令で4レーン同時) c 11 22 33 44 足し算の命令は1回で済む
図3-1 スカラーの繰り返しと SIMD の足し算

左の図のように、通常の(スカラーの)繰り返しでは、足し算を1個ずつ4回行います。右の図の SIMD なら、4つのレーンの足し算を同時に1回で行えます。v * 2 のように、1つの値との計算では、その値が全レーンに使われます。v[0] で、個々のレーンも取り出せます。

整数でも、同じように使えます。

simd_int.mojo
def main():
    var iv = SIMD[DType.int32, 4](1, 2, 3, 4)
    print(iv * iv)
    print(iv % 2, iv // 2)

    var u = SIMD[DType.uint8, 4](250, 251, 252, 253)
    print(u + 10)

    print(iv.cast[DType.float64]())
Ubuntu
~$ mojo run simd_int.mojo
[1, 4, 9, 16]
[1, 0, 1, 0] [0, 1, 1, 2]
[4, 5, 6, 7]
[1.0, 2.0, 3.0, 4.0]

uint8 のレーンの 250 + 10 は 260 ではなく 4 になっています。1節と同じく、あふれはぐるっと回ります。.cast[DType.float64]() は、全レーンの型を一度に変換します。

Scalar は SIMD の仲間

実は、Float64 や Int8 などの数値型は、すべて レーンが1個の SIMD です。Float64 は Scalar[DType.float64] の別名で、Scalar[...] は SIMD[..., 1] のことです。

scalar.mojo
def main():
    var s: Scalar[DType.float64] = 1.5
    var f: Float64 = s
    print(s, f, s == f)

    var a: Scalar[DType.int8] = 5
    var b: Int8 = a
    print(a, b)
    print(DType.float32, DType.int8, DType.bool)
Ubuntu
~$ mojo run scalar.mojo
1.5 1.5 True
5 5
float32 int8 bool

Scalar[DType.float64] の変数を Float64 の変数にそのまま代入できました。同じ型だからです。ここまで学んだ Float64、Int8、UInt32 なども、すべて同じ SIMD の仲間なので、std.math の関数は SIMD にもそのまま使えます。

simd_math.mojo
from std.math import sqrt, floor

def main():
    var v = SIMD[DType.float32, 4](1.0, 4.0, 9.0, 16.0)
    print(sqrt(v))
    print(floor(SIMD[DType.float64, 2](1.5, -1.5)))
Ubuntu
~$ mojo run simd_math.mojo
[1.0, 2.0, 3.0, 4.0]
[1.0, -2.0]

合計・最大値:reduce

全レーンを1つの値にまとめる操作が reduce_... です。

simd_reduce.mojo
def main():
    var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
    print(v.reduce_add())
    print(v.reduce_mul())
    print(v.reduce_max(), v.reduce_min())
    print(len(v), v.dtype)
Ubuntu
~$ mojo run simd_reduce.mojo
10.0
24.0
4.0 1.0
4 float32

reduce_add() は全レーンの合計(1 + 2 + 3 + 4 = 10)です。reduce_mul() は積、reduce_max() と reduce_min() は最大値と最小値です。len(v) でレーンの数、v.dtype で要素の型が分かります。

レーンごとの比較と選択

> などの比較で、全レーンを一度に比べたくなります。ところが、そのまま書くとエラーになります。

simd_gt.mojo
def main():
    var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
    var r = v > 2.0
    print(r)
Ubuntu
~$ mojo run simd_gt.mojo
/home/user/simd_gt.mojo:1:5: error: function instantiation failed
def main():
    ^
/home/user/simd_gt.mojo:3:15: note: call expansion failed
    var r = v > 2.0
              ^
Mojo/stdlib/std/simd.mojo:1504:9: note: constraint failed: Strict inequality is only defined for `Scalar`s; did you mean to use `SIMD.gt(...)`?
mojo: error: failed to run the pass manager

> は1つの値(Scalar)どうしの比較に限られていて、レーンごとの比較には SIMD.gt(...) を使う、という意味のメッセージです。.gt(より大きい)、.lt、.ge、.le、.eq、.ne は、レーンごとに比べた結果を Bool のレーンを持つ SIMD として返します。

simd_mask.mojo
def main():
    var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
    var mask = v.gt(2.0)
    print(mask)
    print(mask.reduce_or(), mask.reduce_and())
    print(mask.select(v, SIMD[DType.float32, 4](0)))
    print(v == v, v.eq(v))
Ubuntu
~$ mojo run simd_mask.mojo
[False, False, True, True]
True False
[0.0, 0.0, 3.0, 4.0]
True [True, True, True, True]

mask は、レーンごとの比較結果(マスク)です。reduce_or() は「どれか1つでも真か」、reduce_and() は「すべて真か」を返します。mask.select(a, b) は、真のレーンは a から、偽のレーンは b から値を取って新しい SIMD を作ります。if を書かずに、レーンごとの条件分岐ができるのです。

最後の行に注意してください。v == v は 全レーンが等しいか を1つの Bool で返し、v.eq(v) はレーンごとの結果を返します。

3.9Python との違いの早見表

項目PythonMojo
整数int(無限の桁)Int(64ビット固定)、Int8〜UInt64
範囲外あふれないぐるっと回る(エラーなし)
小数float(64ビット)Float64、Float32
/(整数どうし)小数を返す(10 / 4 は 2.5)整数を返す(10 / 4 は 2)
// と %負の無限大方向、割る数と同じ符号同じ
0 で割る(小数)ZeroDivisionErrorinf または nan
整数と小数の混在自動で変換エラー。Float64(a) と明示する
math.floor(-2.5)-3(int)-3.0(Float64)
取り込みimport mathfrom std.math import sqrt
複数の値を一括計算NumPy などSIMD

3.10まとめ

確認クイズ

  1. Int.MAX + 1 を計算すると、Mojo 1.2.0 ではどうなりますか?

    解答を見る

    エラーにならず、最小値の -9223372036854775808 になります。Int は64ビット固定で、範囲を超えると反対側の端にぐるっと回ります。

  2. Int の変数 a = 10、b = 4 で a / b を計算すると、Python と Mojo でどう違いますか? -7 // 2 と -7 % 2 はどうですか?

    解答を見る

    Python は 2.5、Mojo は整数の 2 です。小数にしたければ Float64(a) / Float64(b) と書きます。-7 // 2 と -7 % 2 は、どちらも -4 と 1 で同じです。

  3. var f: Float64 = some_int がエラーになるのはなぜで、どう直しますか?

    解答を見る

    Mojo は Int から Float64 への暗黙の変換をしないからです。Float64(some_int) と明示的に変換します。

  4. SIMD[DType.float32, 4] の float32 と 4 は、それぞれ何を表しますか?

    解答を見る

    float32 は要素の型、4 は要素の個数(レーン数)です。この型の値は、Float32 を4つまとめて持ち、足し算などを1回の命令で行えます。

  5. Float64 と Scalar[DType.float64] の関係は?

    解答を見る

    同じ型です。Float64 は、レーンが1個の SIMD である Scalar[DType.float64] の別名です。

練習問題 階乗があふれる場所

Int で n!(1 × 2 × … × n)を求める関数を書き、n が 18 から 23 までの値を表示してください。どの n から、結果がおかしくなりますか?

解答例を見る

次のように書けます。

ex_factorial.mojo
def factorial(n: Int) -> Int:
    var result = 1
    for i in range(2, n + 1):
        result *= i
    return result

def main():
    for n in range(18, 24):
        print(n, factorial(n))
Ubuntu
~$ mojo run ex_factorial.mojo
18 6402373705728000
19 121645100408832000
20 2432902008176640000
21 -4249290049419214848
22 -1250660718674968576
23 8128291617894825984

20! までは正しく、21! から値が壊れます。21! の本当の値は 51090942171709440000 で、Int の最大値より大きいからです。

練習問題 平均を小数で求める

2つの Int の平均を Float64 で返す関数 average(a, b) を書いてください。average(3, 4) が 3.5 になるようにします。

解答例を見る

Int を Float64 に明示的に変換します。

ex_average.mojo
def average(a: Int, b: Int) -> Float64:
    return Float64(a + b) / 2

def main():
    print(average(3, 4))
    print(average(-1, 0))
Ubuntu
~$ mojo run ex_average.mojo
3.5
-0.5

a + b は Int なので、戻り値の型 Float64 に合わせて Float64(...) で包みます。

練習問題 SIMD で内積を求める

SIMD[DType.float32, 4] の2つの値 a = [1, 2, 3, 4]、b = [5, 6, 7, 8] の内積(同じ位置の積の合計)を、SIMD の演算だけで求めてください。

解答例を見る

要素ごとの積をとってから、全レーンを合計します。

ex_dot.mojo
def main():
    var a = SIMD[DType.float32, 4](1, 2, 3, 4)
    var b = SIMD[DType.float32, 4](5, 6, 7, 8)
    print((a * b).reduce_add())
Ubuntu
~$ mojo run ex_dot.mojo
70.0

1×5 + 2×6 + 3×7 + 4×8 = 70 です。a * b は4個の積を1回で計算し、reduce_add() が合計します。