Int
§
Int
类型提供任意大小的整数。它们可以大到你的计算机内存允许的程度,尽管一些实现选择在要求生成真正庞大大小的整数时抛出数值溢出错误。
say 10**600**600# OUTPUT: «Numeric overflow»
与一些语言不同,当两个操作数都是 Int
类型时,使用 /
运算符执行的除法会产生一个分数,不会进行任何舍入。
say 4/5; # OUTPUT: «0.8»
此除法产生的类型是 Rat
或 Num
类型。如果在约简后,分数的分母小于 64 位,则会生成 Rat
,否则会生成 Num
类型。
如果你希望尽可能得到一个 Int
结果,div
和 narrow
函数可能会有所帮助。div
运算符执行整数除法,丢弃余数,而 narrow
将数字拟合到它能拟合的最窄类型。
say 5 div 2; # OUTPUT: «2»# Result `2` is narrow enough to be an Int:say (4/2).narrow; # OUTPUT: «2»say (4/2).narrow.^name; # OUTPUT: «Int»# But 2.5 has fractional part, so it ends up being a Rat type:say (5/2).narrow.^name; # OUTPUT: «Rat»say (5/2).narrow; # OUTPUT: «2.5»# Denominator is too big for a Rat, so a Num is produced:say 1 / 10⁹⁹; # OUTPUT: «1e-99»
Raku 有一个 FatRat
类型,它提供任意精度的分数。为什么在上面的最后一个例子中,会生成一个有限精度的 Num
类型而不是 FatRat
类型?原因是:性能。大多数操作对精度损失一点是可以接受的,因此不需要使用更昂贵的 FatRat
类型。如果你希望获得额外的精度,你需要自己实例化一个。
Num
§
Num
类型提供双精度浮点小数,在其他语言中有时称为“双精度数”。
Num
字面量用字母 e
分隔指数来编写。请记住,即使指数为零,字母 e
也是**必需的**,否则你会得到一个 Rat
有理数字面量。
say 42e0.^name; # OUTPUT: «Num»say 42.0.^name; # OUTPUT: «Rat»
区分大小写的单词 Inf
和 NaN
分别表示特殊值无穷大和非数字。可以使用 U+221E INFINITY (∞
) 字符代替 Inf
。
Raku 尽可能遵循 IEEE 754-2008 浮点运算标准,计划在以后的语言版本中实现更多的一致性。该语言保证为任何给定的 Num
字面量选择最接近的可表示数字,并且确实提供对负零和非规格化数(也称为“次规格化数”)的支持。
请记住,像 say
或 put
这样的输出函数不会非常努力地区分 Numeric
类型的输出方式,并且可能会选择将 Num
显示为 Int
或 Rat
数字。为了获得更明确的输出字符串,请使用 raku
方法。
say 1e0; # OUTPUT: «1»say .5e0; # OUTPUT: «0.5»say 1e0.raku; # OUTPUT: «1e0»say .5e0.raku; # OUTPUT: «0.5e0»
Complex
§
Complex
类型表示复平面的复数。Complex
对象由两个 Num
对象组成,分别表示复数的实部和虚部。
要创建一个 Complex
,你可以在任何其他非复数上使用 i
后缀运算符,可以选择用加法设置实部。要在 NaN
或 Inf
字面量上使用 i
运算符,请用反斜杠将其与它们分开。
say 42i; # OUTPUT: «0+42i»say 73+42i; # OUTPUT: «73+42i»say 73+Inf\i; # OUTPUT: «73+Inf\i»
请记住,上面的语法只是一个加法表达式,优先级规则适用。它也不能用于禁止表达式的场合,例如函数参数中的字面量。
# Precedence of `*` is higher than that of `+`say 2 * 73+10i; # OUTPUT: «146+10i»
为了避免这些问题,你可以选择使用 Complex
字面量语法,它涉及用尖括号括起实部和虚部,没有任何空格。
say 2 * <73+10i>; # OUTPUT: «146+20i»multi how-is-it (<2+4i>)multi how-is-it (|)how-is-it 2+4i; # OUTPUT: «that's my favorite number!»how-is-it 3+2i; # OUTPUT: «meh»
Rational
§
实现 Rational
角色的类型提供高精度和任意精度的十进制数。由于精度越高,性能损失就越大,因此 Rational
类型有两种形式:Rat
和 FatRat
。Rat
是最常用的变体,在大多数情况下会降级为 Num
,当它不再能够保存所有请求的精度时。FatRat
是任意精度的变体,它会不断增长以提供所有请求的精度。
Rat
§
最常见的 Rational
类型。它支持分母最大为 64 位的有理数(在将分数约简到最简分母后)。可以创建分母更大的 Rat
对象,但是,当分母如此大的 Rat
是数学运算的结果时,它们会降级为 Num
对象。
Rat
字面量使用类似于 Num
字面量的语法,在许多其他语言中,使用点来表示数字是小数
say .1 + .2 == .3; # OUTPUT: «True»
如果您尝试在许多常见语言中执行类似于上面的语句,您将得到 False
作为答案,这是由于浮点数学的不精确性。要在 Raku 中获得相同的结果,您必须使用 Num
字面量。
say .1e0 + .2e0 == .3e0; # OUTPUT: «False»
您还可以使用 /
除法运算符 与 Int
或 Rat
对象一起生成 Rat
say 3/4; # OUTPUT: «0.75»say 3/4.2; # OUTPUT: «0.714286»say 1.1/4.2; # OUTPUT: «0.261905»
请记住,上面的语法只是一个除法表达式,优先级规则适用。它也不能在禁止表达式的场合使用,例如例程参数中的字面量。
# Precedence of power operators is higher than divisionsay 3/2²; # OUTPUT: «0.75»
为了避免这些问题,您可以选择使用 Rational
字面量语法,这涉及用尖括号将分子和分母括起来,没有任何空格
say <3/2>²; # OUTPUT: «2.25»multi how-is-it (<3/2>)multi how-is-it (|)how-is-it 3/2; # OUTPUT: «that's my favorite number!»how-is-it 1/3; # OUTPUT: «meh»
最后,任何具有属性 No
的 Unicode 字符,表示分数,都可以用作 Rat
字面量
say ½ + ⅓ + ⅝ + ⅙; # OUTPUT: «1.625»
降级为 Num
§
如果产生 Rat
答案的数学运算会产生分母大于 64 位的 Rat
,则该运算将返回 Num
对象。但是,当构造 Rat
(即它不是某个数学表达式的结果时),可以使用更大的分母
my = 1 / (2⁶⁴ - 1);say ; # OUTPUT: «0.000000000000000000054»say .^name; # OUTPUT: «Rat»say .nude; # OUTPUT: «(1 18446744073709551615)»my = 1 / 2⁶⁴;say ; # OUTPUT: «5.421010862427522e-20»say .^name; # OUTPUT: «Num»my = Rat.new(1, 2⁶⁴);say ; # OUTPUT: «0.000000000000000000054»say .^name; # OUTPUT: «Rat»say .nude; # OUTPUT: «(1 18446744073709551616)»say .Num; # OUTPUT: «5.421010862427522e-20»
FatRat
§
最后一个 Rational
类型——FatRat
——保留您要求的所有精度,将分子和分母存储为两个 Int
对象。一个 FatRat
比一个 Rat
更具传染性,因此许多与 FatRat
的数学运算将产生另一个 FatRat
,保留所有可用的精度。在 Rat
降级为 Num
的地方,使用 FatRat
的数学运算会继续进行
say ((42 + Rat.new(1,2))/999999999999999999).^name; # OUTPUT: «Rat»say ((42 + Rat.new(1,2))/9999999999999999999).^name; # OUTPUT: «Num»say ((42 + FatRat.new(1,2))/999999999999999999).^name; # OUTPUT: «FatRat»say ((42 + FatRat.new(1,2))/99999999999999999999999).^name; # OUTPUT: «FatRat»
没有用于构造 FatRat
对象的特殊运算符或语法。只需使用 FatRat.new
方法,将分子作为第一个位置参数,将分母作为第二个参数。
如果您的程序需要大量创建 FatRat
,您可以创建自己的自定义运算符
sub infix:<🙼>say (1🙼3).raku; # OUTPUT: «FatRat.new(1, 3)»
打印有理数§
请记住,像 say
或 put
这样的输出函数不会非常努力地区分 Numeric
类型的输出方式,并且可能会选择将 Num
显示为 Int
或 Rat
数字。为了获得更明确的输出字符串,请使用 raku
方法。
say 1.0; # OUTPUT: «1»say ⅓; # OUTPUT: «0.333333»say 1.0.raku; # OUTPUT: «1.0»say ⅓.raku; # OUTPUT: «<1/3>»
要获得更多信息,您可以选择在 nude 中查看 Rational
对象,显示其numerator 和denominator
say ⅓; # OUTPUT: «0.333333»say 4/2; # OUTPUT: «2»say ⅓.raku; # OUTPUT: «<1/3>»say <4/2>.nude; # OUTPUT: «(2 1)»
除以零§
在许多语言中,除以零会导致立即异常。在 Raku 中,发生的情况取决于您除以什么以及如何使用结果。
Raku 遵循 IEEE 754-2008 浮点运算标准,但出于历史原因,6.c 和 6.d 语言版本并未完全遵守。 Num
除以零会产生 Failure
,而 Complex
除以零会产生 NaN
分量,无论分子是什么。
从 6.e 版本语言开始,Num
和 Complex
除以零将分别产生 -Inf、+Inf
或 NaN,具体取决于分子是负数、正数还是零(对于 Complex
,实部和虚部都是 Num
,并分别考虑)。
Int
数值的除法会产生一个 Rat
对象(或者一个 Num
,如果在约分后分母大于 64 位,这种情况在除以零时不会发生)。这意味着这种除法永远不会产生 Exception
或 Failure
。结果是一个零分母有理数,它可能是爆炸性的。
零分母有理数§
零分母有理数是一个扮演 Rational
角色的数值,在核心数值中,它将是 Rat
和 FatRat
对象,其分母为零。这种有理数的分子被归一化为 -1
、0
或 1
,具体取决于原始分子是负数、零还是正数。
可以在不进行实际除法的情况下执行的操作是非爆炸性的。例如,您可以分别检查 分子 和 分母 的 裸值,或者执行数学运算,而不会出现任何异常或失败。
将零分母有理数转换为 Num
遵循 IEEE 约定,结果将是 -Inf
、Inf
或 NaN
,具体取决于分子是负数、正数还是零。反过来也是如此:将 ±Inf
/NaN
转换为其中一种 Rational
类型将产生一个具有适当分子的零分母有理数。
say <1/0>.Num; # OUTPUT: «Inf»say <-1/0>.Num; # OUTPUT: «-Inf»say <0/0>.Num; # OUTPUT: «NaN»say Inf.Rat.nude; # OUTPUT: «(1 0)»
所有其他需要对分子和分母进行非 IEEE 除法的操作将导致抛出 X::Numeric::DivideByZero
异常。最常见的此类操作可能是尝试打印或将零分母有理数转换为字符串。
say 0/0;# OUTPUT:# Attempt to divide by zero using div# in block <unit> at -e line 1
同形异义词§
同形异义词 是两种类型的子类,可以表现为其中任何一种。例如,同形异义词 IntStr
是 Int
和 Str
类型的子类,将被任何需要 Int
或 Str
对象的类型约束接受。
同形异义词可以使用 尖括号 创建,可以单独使用,也可以作为哈希键查找的一部分使用;直接使用 .new
方法,也由一些结构提供,例如 sub MAIN
的参数。
say <42>.^name; # OUTPUT: «IntStr»say <42e0>.^name; # OUTPUT: «NumStr»say < 42+42i>.^name; # OUTPUT: «ComplexStr»say < 1/2>.^name; # OUTPUT: «RatStr»say <0.5>.^name; # OUTPUT: «RatStr»= "42";sub MAIN() # OUTPUT: «IntStr»say IntStr.new(42, "42").^name; # OUTPUT: «IntStr»
上面的一些结构在开头的尖括号后面有一个空格。这个空格不是偶然的。通常使用运算符编写的数值,例如 1/2
(Rat
,除法运算符) 和 1+2i
(Complex
,加法),可以写成不涉及运算符的字面量:尖括号没有在尖括号和内部字符之间留任何空格。通过在尖括号内添加空格,我们告诉编译器,我们不仅想要一个 Rat
或 Complex
字面量,而且还希望它是一个同形异义词:在本例中是 RatStr
或 ComplexStr
。
如果数值字面量不使用任何运算符,那么在尖括号内编写它,即使不包含任何空格,也会产生同形异义词。(逻辑:如果你不想要同形异义词,你就不会使用尖括号。对于使用运算符的数字来说,情况并非如此,因为一些结构,例如签名字面量,不允许你使用运算符,所以你不能仅仅为了这些数值字面量而省略尖括号)。
可用的同形异义词§
核心语言提供以下同形异义词
类型 | 同形异义词 | 示例 |
---|---|---|
IntStr | Int 和 Str | <42> |
NumStr | Num 和 Str | <42e0> |
ComplexStr | Complex 和 Str | < 1+2i> |
RatStr | Rat 和 Str | <1.5> |
注意:没有 FatRatStr
类型。
同形词的强制转换§
请记住,同形词只是它们所代表类型的子类。就像一个类型约束为 Foo
的变量或参数可以接受 Foo
的任何子类一样,一个类型约束为 Int
的变量或参数也会接受 IntStr
同形词。
sub foo(Int )foo <42>; # OUTPUT: «IntStr»my Num = <42e0>;say .^name; # OUTPUT: «NumStr»
这也适用于参数 强制转换器
sub foo(Int(Cool) )foo <42>; # OUTPUT: «IntStr»
给定的同形词已经是 Int
类型的对象,因此在这种情况下它不会被转换为“普通” Int
。
如果无法将同形词“折叠”为其组件之一,那么同形词的功能将大大降低。因此,如果您显式调用一个以要强制转换的类型命名的方法,您将只获得该组件。同样适用于任何代理方法,例如调用方法 .Numeric
而不是 .Int
,或者使用 prefix:<~>
运算符 而不是 .Str
方法调用。
my := IntStr.new: 42, "forty two";say .Str; # OUTPUT: «forty two»say +; # OUTPUT: «42»say <1/99999999999999999999>.Rat.^name; # OUTPUT: «Rat»say <1/99999999999999999999>.FatRat.^name; # OUTPUT: «FatRat»
强制转换一整列同形词的一种便捷方法是将 超运算符 应用于适当的前缀。
say map *.^name, <42 50e0 100>; # OUTPUT: «(IntStr NumStr IntStr)»say map *.^name, +«<42 50e0 100>; # OUTPUT: «(Int Num Int)»say map *.^name, ~«<42 50e0 100>; # OUTPUT: «(Str Str Str)»
对象标识§
当我们考虑对象标识时,上面关于强制转换同形词的讨论变得更加重要。一些构造使用它来确定两个对象是否“相同”。虽然对人类来说,同形词 42
和普通 42
可能看起来“相同”,但对这些构造来说,它们是完全不同的对象。
# "42" shows up twice in the result: 42 and <42> are different objects:say unique 1, 1, 1, 42, <42>; # OUTPUT: «(1 42 42)»# Use a different operator to `unique` with:say unique :with(&[==]), 1, 1, 1, 42, <42>; # OUTPUT: «(1 42)»# Or coerce the input instead (faster than using a different `unique` operator):say unique :as(*.Int), 1, 1, 1, 42, <42>; # OUTPUT: «(1 42)»say unique +«(1, 1, 1, 42, <42>); # OUTPUT: «(1 42)»# Parameterized Hash with `Any` keys does not stringify them; our key is of type `Int`:my = 42 => "foo";# But we use the allomorphic key of type `IntStr`, which is not in the Hash:say <42>:exists; # OUTPUT: «False»# Must use curly braces to avoid the allomorph:say :exists; # OUTPUT: «True»# We are using a set operator to look up an `Int` object in a list of `IntStr` objects:say 42 ∈ <42 100 200>; # OUTPUT: «False»# Convert it to an allomorph:say <42> ∈ <42 100 200>; # OUTPUT: «True»# Or convert the items in the list to plain `Int` objects:say 42 ∈ +«<42 100 200>; # OUTPUT: «True»
请注意这些对象标识差异,并根据需要强制转换您的同形词。
转换非同形词§
当我们显式地将变量创建为 Str
或 Numeric
类型时,它们不是 Allomorph
。
my = "010"; say .^name; # OUTPUT: «Str»my = 42; say .^name; # OUTPUT: «Int»
然后我们可以显式地将它们转换为它们的 Allomorph
对应物。
.= Numeric; # OUTPUT: «10»say .^name; # OUTPUT: «Int».= Str; ; # OUTPUT: «42»say .^name; # OUTPUT: «Str»
或者我们可以自然地将它们强制转换为其他形式。
my = "010"; say .^name; # OUTPUT: «Str»my = + 1; # OUTPUT: «11»say .^name; # OUTPUT: «Int»
原生数字§
顾名思义,原生数字提供对原生数字的访问——即您的硬件直接提供的数字。这反过来提供了两个特性:溢出/下溢和更好的性能。
注意:在撰写本文时(2018.05),某些实现(例如 Rakudo)对原生类型提供了一些零星的细节,例如 int64
是否可用以及在 32 位机器上是否为 64 位大小,以及如何在您的程序在这样的硬件上运行时检测到这一点。
可用的原生数字§
原生类型 | 基本数字 | 大小 |
---|---|---|
int | 整数 | 64 位 |
int8 | 整数 | 8 位 |
int16 | 整数 | 16 位 |
int32 | 整数 | 32 位 |
int64 | 整数 | 64 位 |
uint | 无符号整数 | 64 位 |
uint8 | 无符号整数 | 8 位 |
uint16 | 无符号整数 | 16 位 |
uint32 | 无符号整数 | 32 位 |
uint64 | 无符号整数 | 64 位 |
num | 浮点数 | 64 位 |
num32 | 浮点数 | 32 位 |
num64 | 浮点数 | 64 位 |
atomicint | 整数 | 大小适合提供 CPU 提供的原子操作。(通常在 64 位平台上为 64 位,在 32 位平台上为 32 位) |
创建原生数字§
要创建原生类型的变量或参数,只需使用可用数字之一的名称作为类型约束。
my int32 = 42;sub foo(num )class
有时,您可能希望将某个值强制转换为原生类型,而无需创建任何可用的变量。没有 .int
或类似的强制转换方法(方法调用是延迟绑定的,因此它们不适合此目的)。相反,只需使用匿名变量。
some-native-taking-sub( (my int $ = ), (my int32 $ = ) )
溢出/下溢§
尝试赋值一个不适合特定原生类型的值,会产生异常。这包括尝试向原生参数提供过大的参数。
my int = 2¹⁰⁰;# OUTPUT:# Cannot unbox 101 bit wide bigint into native integer# in block <unit> at -e line 1sub f(int ) ; say f 2⁶⁴# OUTPUT:# Cannot unbox 65 bit wide bigint into native integer# in sub f at -e line 1# in block <unit> at -e line 1
然而,修改现有值使其变得过大或过小,会导致溢出或下溢行为。
my int = 2⁶³-1;say ; # OUTPUT: «9223372036854775807»say ++; # OUTPUT: «-9223372036854775808»my uint8 ;say ; # OUTPUT: «0»say -= 100; # OUTPUT: «156»
创建使用原生类型的对象不会涉及程序员的直接赋值;这就是为什么这些结构提供溢出/下溢行为而不是抛出异常。
say Buf.new(1000, 2000, 3000).List; # OUTPUT: «(232 208 184)»say my uint8 = 1000, 2000, 3000; # OUTPUT: «232 208 184»
自动装箱§
虽然它们可以被称为“原生类型”,但原生数值实际上并不是具有任何方法的类。但是,您可以调用这些数值的非原生版本上可用的任何方法。这是怎么回事?
my int8 = -42;say .abs; # OUTPUT: «42»
这种行为被称为“自动装箱”。编译器会自动将原生类型“装箱”到一个具有所有方法的全功能高级类型中。换句话说,上面的int8
被自动转换为Int
,然后是Int
类提供了被调用的abs方法。
当您使用原生类型来提高性能时,这个细节很重要。如果您使用的代码导致执行了大量的自动装箱,那么使用原生类型可能会比使用非原生类型获得更差的性能。
my = -42;my int = -42;# OUTPUT: «0.38180862»# OUTPUT: «0.938720»
如上所示,原生变体速度慢了一倍多。原因是方法调用需要将原生类型装箱,而非原生变体不需要这样做,因此导致性能下降。
在这种特殊情况下,我们可以简单地切换到abs的子程序形式,它可以与原生类型一起使用而无需装箱它们。在其他情况下,您可能需要寻求其他解决方案来避免过度自动装箱,包括将代码的一部分切换到非原生类型。
my = -42;my int = -42;# OUTPUT: «0.38229177»# OUTPUT: «0.3088305»
默认值§
由于原生类型后面没有类,因此没有您通常在未初始化的变量中获得的类型对象。因此,原生类型会自动初始化为零。在 6.c 语言中,原生浮点类型(num
、num32
和 num64
)被初始化为值NaN
;在 6.d 语言中,默认值为0e0
。
原生分派§
可以将原生候选者与非原生候选者一起使用,例如,当大小可预测时,使用原生候选者提供更快的算法,但在其他情况下则回退到较慢的非原生备选方案。以下是在涉及原生候选者的多重分派方面的规则。
首先,原生类型的尺寸在分派中不起作用,int8
被认为与int16
或int
相同。
multi foo(int )multi foo(int32 )foo my int = 42;# OUTPUT:# Ambiguous call to 'foo(Int)'; these signatures all match:# :(int $x)# :(int32 $x)
其次,如果一个例程是only
——即它不是一个multi
——它接受一个非原生类型,但在调用期间给出了一个原生类型,反之亦然,那么参数将被自动装箱或自动拆箱以使调用成为可能。如果给定的参数太大而无法放入原生参数中,则会抛出异常。
-> int ( 42 ); # OK; auto-unboxing-> int ( 2¹⁰⁰ ); # Too large; exception-> Int ( 2¹⁰⁰ ); # OK; non-native parameter-> Int ( my int $ = 42 ); # OK; auto-boxing
当涉及到multi
例程时,如果不存在原生候选者来接受原生参数,则原生参数将始终被自动装箱。
multi foo (Int )say foo my int $ = 42; # OUTPUT: «42»
当以相反的方式进行时,不会提供相同的便利。如果只有一个原生候选者可用,则非原生参数不会被自动拆箱,而是会抛出一个指示没有候选者匹配的异常(这种不对称的原因是原生类型始终可以被装箱,但非原生类型可能太大而无法放入原生类型中)。
multi f(int )my = 2;say f ;# OUTPUT:# Cannot resolve caller f(Int); none of these signatures match:# (int $x)# in block <unit> at -e line 1
但是,如果调用中有一个参数是原生类型,而另一个参数是数字字面量,则此规则将被放弃。
multi f(int, int)f 42, my int ; # Successful call
这样您就不必不断地编写,例如,$n +> 2
作为 $n +> (my int $ = 2)
。编译器知道字面量足够小以适合原生类型,并将其转换为原生类型。
原子操作§
该语言提供了一些操作,这些操作保证以原子方式执行,即可以安全地由多个线程执行,而无需锁定,并且没有数据竞争的风险。
对于此类操作,需要使用 atomicint
原生类型。此类型类似于普通的原生 int
,但它的大小经过调整,以便可以对其执行 CPU 提供的原子操作。在 32 位 CPU 上,它通常大小为 32 位,而在 64 位 CPU 上,它通常大小为 64 位。
# !!WRONG!! Might be non-atomic on some systemsmy int ;await ^100 .map: ;say ; # OUTPUT: «98»# RIGHT! The use of `atomicint` type guarantees operation is atomicmy atomicint ;await ^100 .map: ;say ; # OUTPUT: «100»
int
的相似性也存在于多重分派中:atomicint
、普通 int
和大小为 int
的变体都被分派器视为相同,无法通过多重分派进行区分。
数值传染性§
数值“传染性”决定了当两个不同类型的数值参与某些数学运算时,结果的类型。如果结果是该类型的而不是另一个操作数的类型,则称该类型比另一个类型更具传染性。例如,Num
类型比 Int
更具传染性,因此我们可以预期 42e0 + 42
会产生一个 Num
作为结果。
传染性如下所示,最具传染性的类型列在最前面
Complex
Num
FatRat
Rat
Int
say (2 + 2e0).^name; # Int + Num => OUTPUT: «Num»say (½ + ½).^name; # Rat + Rat => OUTPUT: «Rat»say (FatRat.new(1,2) + ½).^name; # FatRat + Rat => OUTPUT: «FatRat»
同形词与其数值组件具有相同的传染性。原生类型会自动装箱,并与其装箱变体具有相同的传染性。