第3章
数値型と SIMD 入門
Mojo の整数・浮動小数点数・Bool の性質を、Python との違いに注目して学びます。オーバーフロー、型の変換、math モジュール、そして1回の命令で複数の値を計算する SIMD の入り口までを扱います。
この章の内容
Python の int は、どれだけ大きくなってもあふれません。ところが Mojo の整数は、サイズが決まっています。この違いは、数値を扱うあらゆる場面に影響します。この章では、Mojo の数値の型を一通り見て、Python の感覚のままだとどこでつまずくのかを、実際に動かして確かめます。
この章を終えると、次のことができるようになります。
Intがオーバーフローしたとき、何が起きるかを説明できるInt8〜Int64、UInt8〜UInt64の範囲を知り、使い分けの考え方を説明できる- 浮動小数点数の誤差と、
Float32とFloat64の違いを説明できる /、//、%、**の動きを、Python と比べて説明できる- 型を明示的に変換し、暗黙の変換が拒否されたときのエラーを読める
std.mathの関数を使えるSIMD型で、複数の値をまとめて計算できる
文字列は第4章、性能の測り方は第19章と第20章で扱います。この章では「正しく動く」ことに集中します。
3.1Int は 64 ビット固定の整数
まず、Python と同じつもりで、とても大きな整数を作ってみましょう。
def main():
print(Int.MAX)
print(Int.MIN)
print(Int.MAX + 1)~$ 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 の最大値より大きくなります。
def main():
var x: Int = 3037000500
print(x * x)
var base: Int = 2
print(base ** 62)
print(base ** 63)
print(base ** 64)~$ 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 Int64 | 8 / 16 / 32 / 64 | あり |
UInt8 UInt16 UInt32 UInt64 | 8 / 16 / 32 / 64 | なし |
Int | 64(この環境では) | あり |
範囲は、型の MAX と MIN で確かめられます。サイズ(バイト数)は size_of で分かります。
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]())~$ 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 で、画像のピクセルの明るさなどによく使われます。
範囲を超えたときの動き
サイズの小さい型では、あふれやすくなります。
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))~$ 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 と同じ形式です。
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)))~$ 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
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]())~$ 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 を表す型です。
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))~$ 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数値リテラルの書き方
数をプログラムの中に直接書いたものを リテラル と呼びます。
def main():
print(1_000_000)
print(0xFF, 0b101, 0o17)
print(1e3, 1.5e-3)
print(3, 3.0)~$ mojo run literals.mojo 1000000 255 5 15 1000.0 0.0015 3 3.0
- 桁の区切りに
_を入れられます(1_000_000)。 - 16進数は
0x、2進数は0b、8進数は0oで始めます。表示は10進数です。 1e3や1.5e-3のような指数表記は、小数として扱われます。1e3は1000.0と表示されました。
ここまでは Python と同じです。違いは、リテラルの型が 使われる場所で決まる ことです。var x: Int8 = 5 と書けば 5 は Int8 になり、var y: Float32 = 5 と書けば Float32 になります。何も指定しなければ、整数は Int、小数は Float64 になります。
3.5算術演算子
+、-、*、/、//、%、** と、ビット演算の &、|、^、~、<<、>> があります。割り算の仲間は、負の数が入ると結果が言語によって変わるので、実際に確かめましょう。
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)~$ 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 でやってみます。
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)~$ 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 では整数どうしの/も小数を返し、-7 / 2は -3.5、10 / 4は 2.5 です。Mojo のInt / Intは 整数を返し、-7 / 2は -3、10 / 4は 2 でした。しかも-7 // 2の -4 ではなく -3 なので、//のような切り下げではなく、0 に近づく切り捨てです。小数の結果が欲しいときは、Float64(a) / Float64(b)のように先に変換します(2行目の -3.5)。//は、結果を 負の無限大の方向に切り下げます。-7 // 2は -3 ではなく -4 です。C や Java のような「0に近づける」割り算とは違い、Python と同じです。%の結果は、割る数と同じ符号になります。-7 % 2は 1、7 % -2は -1 で、Python と同じです。//と%は、(a // b) * b + a % b == aの関係を保ちます。
// と % の約束は Python とそろっていますが、/ だけは違う、と覚えておきましょう。
** は累乗です。1節で見たように、整数の累乗もあふれます。小数の ** は、2.0 ** -1 のように負の指数も扱えます。
3.6型を変換する
Python では 1 + 2.5 のように、整数と小数を混ぜて計算できました。Mojo では、変数の型が違うと自動では変換されません。
暗黙の変換は拒否される
まず、Int の変数を Int8 の変数に入れようとしてみます。
def main():
var n: Int = 300
var small: Int8 = n
print(small)~$ 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 の間も同じです。
def main():
var a: Int = 3
var f: Float64 = a
print(f)
var g: Float64 = 2.5
var k: Int = g
print(k)~$ 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
どちらの向きも拒否されます。エラーは、問題のある行ごとに報告されました。
計算の中で型を混ぜたときも、エラーになります。
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)~$ 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 のような、整数どうしでも同じです。
明示的に変換する
変換したいときは、型の名前を関数のように使って、自分で書きます。
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)~$ mojo run convert.mojo 5.5 3 -3 44 300 44 305
Float64(a)でIntをFloat64にします。Int(g)は小数部分を 切り捨てて 0 に近づけます。3.9 は 3、-3.9 は -3 です。Python のint(3.9)と同じ動きです。Int8(n)のように小さい型への変換は、範囲に入らなければ切り捨てられます(Int8(300)は 44)。明示的に書いた以上、「承知の上で変換する」という扱いです。範囲の確認は自分で行います。
3.7math モジュール
平方根や切り上げなどは、標準ライブラリの std.math にあります。Python の import math に対応しますが、Mojo では from std.math import ... と、名前を指定して取り込みます。
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)))~$ 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
sqrt(平方根)、floor(切り下げ)、ceil(切り上げ)、trunc(0に近づける切り捨て)は、小数を受け取って小数を返します。Python のmath.floor(-2.5)が整数 -3 を返すのと違い、Mojo のfloor(-2.5)は-3.0です。piは定数です。round、abs、min、maxは、取り込まなくても使える組み込み関数です。round(2.5)は 2.0 で、Python のround(2.5)と同じく、ちょうど .5 の値は偶数側に丸められます。sqrt(Float32(2.0))のようにFloat32を渡すと、Float32の精度で計算し、表示も短くなります。
3.8SIMD 入門:複数の値を1度に計算する
ここまでの数値は、1つの値でした。Mojo には、同じ種類の値を複数まとめて持ち、1回の命令で計算できる型 があります。それが SIMD です。SIMD は Single Instruction, Multiple Data(1つの命令で複数のデータ)の略です。
CPU には、たとえば Float32 を4個ならべて、4個ぶんの足し算を1回でこなす命令があります。SIMD 型は、その仕組みを直接使うための型です。
SIMD 値を作る
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])~$ 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個の足し算を一度に行います。
左の図のように、通常の(スカラーの)繰り返しでは、足し算を1個ずつ4回行います。右の図の SIMD なら、4つのレーンの足し算を同時に1回で行えます。v * 2 のように、1つの値との計算では、その値が全レーンに使われます。v[0] で、個々のレーンも取り出せます。
整数でも、同じように使えます。
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]())~$ 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] のことです。
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)~$ 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 にもそのまま使えます。
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)))~$ mojo run simd_math.mojo [1.0, 2.0, 3.0, 4.0] [1.0, -2.0]
合計・最大値:reduce
全レーンを1つの値にまとめる操作が reduce_... です。
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)~$ 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 で要素の型が分かります。
レーンごとの比較と選択
> などの比較で、全レーンを一度に比べたくなります。ところが、そのまま書くとエラーになります。
def main():
var v = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
var r = v > 2.0
print(r)~$ 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 として返します。
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))~$ 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 との違いの早見表
| 項目 | Python | Mojo |
|---|---|---|
| 整数 | int(無限の桁) | Int(64ビット固定)、Int8〜UInt64 |
| 範囲外 | あふれない | ぐるっと回る(エラーなし) |
| 小数 | float(64ビット) | Float64、Float32 |
/(整数どうし) | 小数を返す(10 / 4 は 2.5) | 整数を返す(10 / 4 は 2) |
// と % | 負の無限大方向、割る数と同じ符号 | 同じ |
| 0 で割る(小数) | ZeroDivisionError | inf または nan |
| 整数と小数の混在 | 自動で変換 | エラー。Float64(a) と明示する |
math.floor(-2.5) | -3(int) | -3.0(Float64) |
| 取り込み | import math | from std.math import sqrt |
| 複数の値を一括計算 | NumPy など | SIMD |
3.10まとめ
Intは64ビット固定の整数です。範囲を超えるとエラーにならずに、ぐるっと回ります。Int8〜Int64とUInt8〜UInt64があり、MAXとMINで範囲を調べられます。Float64が標準の小数、Float32は精度が低い代わりに小さく、速く計算できます。0.1 + 0.2 の誤差は Python と同じです。Int / Intは整数(0に近づく切り捨て)を返し、Python と違います。//は切り下げ、%は割る数と同じ符号で、こちらは Python と同じです。- 型の違う値は、自動では変換されません。
Float64(a)、Int8(n)のように、自分で明示します。 - 数学の関数は
from std.math import ...で取り込みます。 SIMD[DType.float32, 4]は、同じ型の値を複数まとめて、1回の命令で計算します。Float64などは、レーンが1個の SIMD です。- レーンごとの比較は
.gt()などを使い、selectで条件分岐し、reduce_add()で合計します。
確認クイズ
Int.MAX + 1を計算すると、Mojo 1.2.0 ではどうなりますか?解答を見る
エラーにならず、最小値の -9223372036854775808 になります。
Intは64ビット固定で、範囲を超えると反対側の端にぐるっと回ります。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で同じです。var f: Float64 = some_intがエラーになるのはなぜで、どう直しますか?解答を見る
Mojo は
IntからFloat64への暗黙の変換をしないからです。Float64(some_int)と明示的に変換します。SIMD[DType.float32, 4]のfloat32と4は、それぞれ何を表しますか?解答を見る
float32は要素の型、4は要素の個数(レーン数)です。この型の値は、Float32を4つまとめて持ち、足し算などを1回の命令で行えます。Float64とScalar[DType.float64]の関係は?解答を見る
同じ型です。
Float64は、レーンが1個の SIMD であるScalar[DType.float64]の別名です。
練習問題 階乗があふれる場所
Int で n!(1 × 2 × … × n)を求める関数を書き、n が 18 から 23 までの値を表示してください。どの n から、結果がおかしくなりますか?
解答例を見る
次のように書けます。
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))~$ 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 に明示的に変換します。
def average(a: Int, b: Int) -> Float64:
return Float64(a + b) / 2
def main():
print(average(3, 4))
print(average(-1, 0))~$ 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 の演算だけで求めてください。
解答例を見る
要素ごとの積をとってから、全レーンを合計します。
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())~$ mojo run ex_dot.mojo 70.0
1×5 + 2×6 + 3×7 + 4×8 = 70 です。a * b は4個の積を1回で計算し、reduce_add() が合計します。