第三章. 数字

Numbers

声明

本章翻译仅用于 Raku 学习和研究, 请支持电子版或纸质版

第三章. 数字

This chapter steps back from the breadth of the previous chapter to focus on the idea of numbers and their representation in your programs. Raku supports several types of numbers and works hard to keep them exact as long as it can.

本章从前一章的广度开始,重点介绍数字的概念及其在程序中的表示。 Raku 支持多种类型的数字,并且尽可能地努力保持它们的准确性。

Number Types

Not all numbers are created alike. You’ve seen whole numbers, how to do basic mathematical operations on them, and how to compare them. But whole numbers are just one of the numeric types. You can see what type a number is by calling .^name on it:

并非所有数字都是相同的。你已经看过整数,如何对它们进行基本的数学运算,以及如何比较它们。但整数只是数字类型之一。你可以通过调用 .^name 来查看数字的类型:

% raku
> 137.^name
Int

That’s an Int, short for “integer”—whole numbers, positive or negative, and zero. The compiler recognizes it because it has decimal digits; it parses it and creates the object for you. But try it with a negative number:

这是一个 Int,是“整数”的缩写 - 整数,正数或负数,以及零。编译器识别它,因为它有十进制数字;它解析它并为你创建对象。但尝试使用负数:

% raku
> -137.^name
Cannot convert string to number

The minus sign isn’t actually part of the number. It’s an operator (a unary prefix one) that negates the positive number. That means that -137 isn’t a term; it’s an expression. The .^name happens first and evaluates to Intas before. When - tries to negate the type name it realizes it can’t do that and complains. You can fix the ordering problem with parentheses—things inside parentheses happen before those outside:

减号实际上不是数字的一部分。它是一个运算符(一个一元前缀)否定正数。这意味着 -137 不是一个项;这是一个表达式。 .^name 首先发生,并计算为之前的 Int。当 - 试图否定类型名称时,它意识到它无法做到并抱怨。你可以使用括号修复顺序问题 - 括号内的事情发生在外部之前:

% raku
> (-137).^name

There are other types of numbers, some of which are shown in Table 3-1.

还有其他类型的数字,其中一些如表3-1所示。

Value Class Description
137 Int 正整数 (whole number)
-17 Int 负整数 (whole number)
3.1415926 Rat 分数
6.026e34 Num 科学计数法
0+i Complex 带有实部和虚部的复数

EXERCISE 3.1Call .^name on some of the other kinds of numbers from Table 3-1. What other sorts of numbers does Raku support? Which ones need parentheses to group them?

练习3.1Call。^表3-1中其他一些数字的名称。 Raku支持哪些其他类型的数字?哪些人需要括号将它们分组?

Integers

The integers are the whole numbers. You’ve seen that they can be represented in many ways and in different bases:

整数是整数。你已经看到它们可以通过多种方式和不同的基础来表示:

137
-19
0x89
:7<254>

Including underscores between digits can make larger numbers easier to read. They aren’t part of the number and can only come between digits (so, not two in a row). You could separate by thousands:

在数字之间加上下划线可以使更大的数字更容易阅读。它们不是数字的一部分,只能在数字之间(因此,不是连续两个)。你可以分成几千:

123_456_789

Two hexadecimal digits represent an octet; it’s easy to see those when you have underscores between pairs ofdigits:

两个十六进制数字代表一个八位字节;当你在数字对之间有下划线时很容易看到它们:

0x89_AB_CD_EF

类型约束

When you declare a variable without assigning to it there’s still “something” there. It’s a type object with the type Any—a generic type that’s the basis for most Raku objects:

当你声明一个变量但没有为其赋值时,变量里仍然存在“某些东西”。它是一个类型为Any类型的类型对象,它是大多数 Raku 对象的基础:

my $number;
put $number.^name;  # Any

If you coerce Any to a Boolean value you get False. Any type object is undefined, but this is slightly more undefined because it’s a general class.

When you want to assign to a container that holds a type object you have to replace it with a value of the same type or something based on that type. You can replace Any with almost any literal value:

如果你强转Any为布尔值,你会得到 False。任何类型对象都是未定义的,但这稍微未定义,因为它是一个通用类。

如果要给包含类型对象的容器赋值,则必须使用相同类型的值或基于该类型的值替换它。你可以使用几乎任何字面值替换Any

my $number;      # starts as Any
$number = 137;
$number = 'Hamadryas';

That’s the same as explicitly constraining the value to the Any type. Without an assignment the variable gets the type object of its constraint:

这与显式约束Any类型的值相同。如果没有赋值,变量将获取其约束的类型对象:

my Any $number;  # starts as Any
$number = 137;
$number = 'Hamadryas';

You can be as specific as you like. If your variable should only store an integer you can use the Int type to constrain it even before you assign to it. Even without a value it knows its type:

你可以随心所欲。如果你的变量只应存储一个整数,则可以使用 Int 类型在赋值给它之前对其进行约束。即使没有值,它也知道它的类型:

my Int $number;
put $number2.^name;  # Int

Whatever you assign to it must be an Int (or something derived from an Int):

无论你给它赋什么值,它必须是一个 Int(或从 Int 派生的东西):

my Int $number;
$number = 137;
$number = 'Hamadryas';  # NOPE! Error

When you try to assign something that is not the correct type you get an error:

当你尝试分配不正确类型的内容时,你会收到错误:

Type check failed in assignment to $n; expected Int but got Str

This check happens when you assign to the variable. Raku calls this “gradual typing.” You don’t have to use it until you want it, but you still have to be careful to obey it when you do use it. The compiler can’t catch all type errors before you run the program.

You can use types in your MAIN signature too. These types apply to the command-line arguments. If you don’t supply appropriate values you’ll get an error right away:

给变量赋值时会发生此检查。 Raku 将此称为“渐进类型”。直到你需要它时才使用,但是在使用它时仍然需要小心遵守它。在运行程序之前,编译器无法捕获所有类型错误。

你也可以在 MAIN 签名中使用类型。这些类型应用于命令行参数。如果你没有提供合适的值,你将立即收到错误:

sub MAIN ( Int $n ) { ... }

EXERCISE 3.2Create a program that takes two arguments from the command line and outputs their types. Try it with numbers and text in each position. What types do you get?

When you ran your program for the previous exercise you saw the type name IntStr. This is an allomorph—a type that’s both an Int and a Str at the same time.

All of the command-line arguments are actually text, even though some of them look like numbers. There’s a hidden val routine that inspects the arguments and turns those that look like some sort of number into the appropriate allomorph. This type has the behavior of both numbers and strings at the same time. You might think that’s a little weird at first, but it’s one of the things that allows a language such as Raku to easily process text.

练习3.2 创建一个从命令行获取两个参数并输出其类型的程序。在每个位置尝试使用数字和文本。你得到什么类型?

当你为上一个练习运行程序时,你看到了类型名称 IntStr。这是一个同质异形的类型,它同时是 IntStr

所有命令行参数实际上都是文本,即使它们中的一些看起来像数字。有一个隐藏的 val 例程,它检查参数并将那些看起来像某种数字的数字转换为适当的同质异形。此类型同时具有数字和字符串的行为。你可能认为一开始有点奇怪,但这是允许像 Raku 这样的语言轻松处理文本的事情之一。

智能匹配

智能匹配运算符 ~~ 代表了许多种比较,并为其操作数选择了正确的比较。~~ 的左侧是值或变量,~~ 的右侧是类型,如果值是该类型或从该类型派生的,则返回 True

% raku
> 1 ~~ Int
True
> 1.^mro
((Int) (Cool) (Any) (Mu))
> 1 ~~ Cool
True
> 1 ~~ Any
True

这是有效的,因为字面量是隐式创建的对象并知道它们是什么。将其与任何其他类型进行比较,即使该值可能是该类型的合法值,也会返回 False

% raku
> 1 ~~ Complex
False

但是,你可以使用以所需类型命名的强转方法轻松地转换数字类型(如果类型提供了一个):

% raku
To exit type 'exit' or '^D'
> 1.Complex
1+0i
> 1.Complex ~~ Complex
True

Smart matching is easy with given-when. Two things happen with this feature. First, given binds $_ to the value of the variable you specify. The $_ is the topic; this allows you to write some code that uses $_ to process the current thing you care about without knowing what that thing is.

Second, when looks at the condition you supply. If there’s no explicit comparator it smart matches $_ against the value you gave it. The first when block that is satisfied is the one that wins. A default block (no condition!) catches it when no when does.

The conditions for these whens are type objects to smart match against $_:

有了 given-when 智能匹配就很容易了。这个功能发生了两件事。首先,given$_ 绑定到你指定的变量的值上。 $_ 是主题;这允许你编写一些使用 $_ 的代码来处理你正关心的当前事物,而不需知道那是什么东西。

其次,when 查找你提供的条件。如果没有显式的比较器,它将 $_ 与你给出的值进行智能匹配。第一个满足的 when 块是获胜的块。没有时,default 块(无条件!)会捕获它。

这些 when 的条件是与 $_ 智能匹配的类型对象:

given $some-number {
    when Int     { put 'Saw an integer' }
    when Complex { put 'Saw a complex number' }
    when Rat     { put 'Eek! Saw a rat!' }
    default      { put 'Saw something' }
}

Making everything explicit, you’d get something like this mess of repeated typing:

要让一切都清楚,你会得到类似这种重复输入的东西:

given $some-number -> $_ {
    when $_ ~~ Int     { put 'Saw an integer' }
    when $_ ~~ Complex { put 'Saw a complex number' }
    when $_ ~~ Rat     { put 'Eek! Saw a rat!' }
    default            { put 'Saw something' }
}

You can make this shorter using do in the same way you did with if. The last evaluated expression becomes the value of the entire given structure:

使用 do 可以使用与 if 相同的方式缩短代码。最后计算的表达式成为整个 given 结构的值:

put 'Saw ', do given $some-number {
    when Int     { 'an integer' }
    when Complex { 'a complex number' }
    when Rat     { 'a rat! Eek!' }
    default      { 'something' }
}

EXERCISE 3.3Using given, create a program that reports the type of number you specify on the command line. Try it with arguments such as 17, 17.0, 17i, and Hamadryas.

There’s another interesting thing you can do with $_. A method call dot with no object to the left uses $_ as the object:

练习3.3 使用 given,创建一个程序,报告你在命令行上指定的数字类型。尝试使用诸如 17,17.0,17iHamadryas 之类的参数。

你可以用 $_ 做另一个有趣的事情。左边没有对象的方法调用点使用 $_ 作为对象:

$_.put;
.put;

put $_.roots unless $_.is-prime;
put .roots unless .is-prime;

You can use a postfix given to set $_ for a single statement to avoid typing out a variable multiple times. You’ll see the implicit topic much more as you go through the book:

你可以使用后缀 given 为单个语句设置 $_,以避免多次输入同一变量。在阅读本书时,你会多次看到隐式的主题:

my $some-number = 19;
put .^name, ' ', .is-prime given $some-number;

有理数

Raku represents nonwhole numbers as fractions using integers. You might literally represent it as a number with a decimal point (sometimes called a floating-point number), but the compiler turns that into a fraction. You can see the numerator and denominator for that reduced fraction:

Raku 使用整数表示非全数字作为分数。你可能会将其表示为带小数点的数字(有时称为浮点数),但编译器将其转换为分数。你可以看到化简后的分数的分子和分母:

% raku
> 3.1415926
3.1415926
> 3.1415926.^name
Rat
> 3.1415926.numerator
15707963
> 3.1415926.denominator
5000000

EXERCISE 3.4 Create a program that takes a single decimal number command-line argument and shows it to you as a fraction. What are the numerator and denominator?

You can add rational numbers to get another fraction; Raku does the work for you:

练习3.4 创建一个接受单个十进制数字命令行参数的程序,并将其作为分数显示给你。分子和分母是什么?

你可以添加有理数来得到另一个分数; Raku 为你完成工作:

% raku
> 1/7 + 1/3
0.476190

The .perl method shows you how Raku thinks about it. You can see the fraction with the least common multiple in the denominator:

.perl 方法向你展示 Raku 如何思考它。你可以看到这个分数的分母中有最小公倍数:

% raku
> (1/7 + 1/3).perl
<10/21>

It didn’t divide the numbers then try to store the result as a floating-point number; that would lose accuracy. It keeps it as an exact fraction as long as it can. This means that these sums are exactly right.

Try this in your favorite programming language:

它没有除以数字,然后尝试将结果存储为浮点数;那会失去准确性。只要它可以,它就将它保持为精确的分数。这意味着这些总和是完全正确的。

用你最喜欢的编程语言试试这个:

% raku
> 0.1 + 0.2
0.3

Another way to define a Rat is to write it as a literal fraction inside angle brackets, <>:

定义 Rat 的另一种方法是将其写为尖括号内的字面量分数,<>

% raku
> <10/21>
0.476190
> <10/21>.^name
Rat
> <10/21>.perl
<10/21>

It’s the same in a program:

它在程序中是相同的:

my $seventh = <1/7>;
my $third   = <1/3>;

my $added = $seventh + $third;

put $added.perl;

You can’t do this with a variable inside the angle brackets. You’ll see what’s going on in the next chapter, but inside the <> that’s not really a variable. The $ is a literal character:

你无法使用尖括号内的变量执行此操作。你将在下一章中看到会发生什么事情,但在 <> 里面它并不是真正的变量。在 <> 里面 $ 是一个字面量字符:

% raku
> <1/$n>
1/$n

At some point the fractions will be too large and you’ll get an error. Here’s a program that adds the reciprocals of the powers of two. It uses loop and uses ++ to make higher powers of two:

在某些时候,分数将太大,你会得到一个错误。这是一个程序,它将 2 的幂的倒数相加。它使用 loop 并使用 ++ 来获得 2 的更高的幂:

my $n   = 0;
my $sum = 0;
loop {
    $sum += 1 / 2**$n++;
    put .numerator, '/', .denominator, ' = ' given $sum;
}

You get progressively larger fractions even though this series converges on 2. Eventually it fails because the denominator is limited to a 64-bit integer size:

即使该系列收敛于 2,你也会逐渐获得更大的分数。最终它会失败,因为分母限制为 64 位整数:

% raku converging.p6
1/1 = 1
3/2 = 1.5
7/4 = 1.75
15/8 = 1.875
31/16 = 1.9375
63/32 = 1.96875
...
4611686018427387903/2305843009213693952 = 2
9223372036854775807/4611686018427387904 = 2
18446744073709551615/9223372036854775808 = 2
No such method 'numerator' for invocant of type 'Num'.

There’s another class that can handle this. A FatRat is a fraction with an arbitrarily large denominator. This is the first time you get to construct an object directly. Call the .new method with the numerator and denominator:

还有另一个类可以处理这个问题。 FatRat 是具有任意大分母的分数。这是你第一次直接构造对象。使用分子和分母调用 .new 方法:

my $sum = FatRat.new: 0, 1;

If you have an existing Rat you can turn it into a FatRat with a method. You’d do that when you know you are going to need it later when you do math with another FatRat:

如果你有一个现有的 Rat,可以使用方法将其转换为 FatRat。当你用另一个FatRat 做数学时,如果你知道以后需要它,你会这样做:

my $fatrat = <10/21>.FatRat;

When you need to add a FatRat to the existing one, you can construct that one in the same way:

当你需要将 FatRat 添加到现有的 FatRat 时,你可以以相同的方式构造它:

FatRat.new: 1, 2**$n++

Otherwise the program is the same, although this version will run much longer. Notice that all the fractions need to be FatRats to keep it going:

否则程序是相同的,虽然这个版本将运行更长时间。请注意,所有分数都需要 FatRat 才能保持运行:

my $n   = 0;
my $sum = FatRat.new: 0, 1;
loop {
    $sum += FatRat.new: 1, 2**$n++;
    put $sum.^name;
    put .numerator, '/', .denominator, ' = ', $_ given $sum;
}

EXERCISE 3.5Create a program that sums the series of fractions 1, 1/2, 1/3, and so on. This is the harmonic series. Calculate the partial sum up to a denominator of 100. Output the value at each stage of the sum.

练习3.5 创建一个程序,对一系列分数 1,1/2,1/3 等求和。这是谐波系列。计算分母总和为 100 的分母。在总和的每个阶段输出值。

Imaginary and Complex Numbers

Imaginary numbers are multiples of the square root of –1. Impossible, you say? I’m not going to explain that in this book but Raku has them. If you’re an electrical engineer you’ve likely run into complex numbers when modeling certain properties.

Raku has a term for the imaginary unit; it’s i. The number 5i is imaginary; it’s five times the imaginary unit. Try it in the REPL:

虚数是 -1 的平方根的倍数。你说不可能吗?我不打算在本书中解释,但 Raku有它们。如果你是电气工程师,在对某些属性进行建模时可能会遇到复数。

Raku 有一个虚数单位的术语;它是 i。数字 5i 是虚拟的;它是虚部单位的五倍。在 REPL 中尝试:

% raku
> 5i
0+5i
> 5*i
0+5i
> 5\i
0+5i
> 5\ i
0+5i

That was four ways to write the same thing. The first way puts two terms, 5 and i, next to each other with no whitespace or separator. That works and is likely to be the way you’ll type it most of the time. The second multiplies 5 and i to get the same result. The last two use \ to create unspace. One has no space and the other has some space.

You can’t have only whitespace between the digits and the i, or the compiler will think that you have terms in a row (because you do):

这是写同样事情的四种方式。第一种方法是将两个项 5i 放在一起,没有空格或分隔符。这很有效,很可能是你大部分时间打字的方式。第二个乘以 5i 得到相同的结果。最后两个使用 \ 来创建 unspace。一个没有空格,另一个有空格。

你不能只有数字和 i 之间的空格,否则编译器会认为你有连续的项(因为你这样做):

% raku
> 5 i
===SORRY!=== Error while compiling:
Two terms in a row
------> 5⏏ i

When you tried the imaginary number 5i in the REPL you got back 0 + 5i. That’s a real number added to an imaginary number. Taken together they form a complex number that has real and imaginary parts.

To get the real or imaginary parts of the number you can use the .re or .im methods, which take their short names from the common math notation:

当你在 REPL 中尝试了虚数 5i 时,你得到了 0 + 5i。这是实数和虚数相加。总之,它们一块儿形成了一个具有实部和虚部的复数。

要获取数字的实部或虚数部分,可以使用 .re.im 方法,这些方法的常用数学符号采用短名称:

% raku
> my $z = 137+9i;
137+9i
> $z.^name
Complex
> $z.re
137
> $z.im
9

You can add, subtract, and multiply Complex numbers. Multiplication involves cross terms with the real part of one number multiplied by the imaginary part of the other:

你可以相加,减去和乘以复数。乘法涉及交叉项,其中一个数字的实部乘以另一个数的虚部:

% raku
> (5+9i) * (6+3i)
3+69i
> (5+9i) + (6+3i)
11+12i
> (5+9i) - (6+3i)
-1+6i
> (5+9i) / (6+3i)
1.26666666666667+0.866666666666667i

You can even multiply i by itself:

你甚至可以让 i 和自身相乘:

% raku
> i*i
-1

Numbers Small and Large

Everything that doesn’t fit into the specific numeric types is in the general Num type. The number e (the natural base) is one of those numbers:

所有不符合特定数字类型的东西都是通用的 Num 类型。数字 e(自然基数)就是这些数字之一:

% raku
> e.^name
Num
> e
2.71828182845905

You can also use infinities. Putting all the nuances and uses aside, for this book Inf is just something that’s larger than any integer. You’ll see it in use later:

你也可以使用无穷大。把所有的细微差别和用法放在一边,对于这本书,Inf 只是比任何整数都大的东西。你会在后面看到它在使用:

% raku
> Inf.^name
Num
> (-Inf).^name
Num

You can write numbers in exponential notation. You can specify a power of 10 after an e of either case. This is a different sort of e than the term you just saw:

你可以用指数表示法编写数字。在任一情况下,你都可以在 e 后面指定 10 的幂。这与你刚刚看到的术语不同:

6.02214e23
6.02214E23

These are the same as multiplying the number by a power of 10 that you construct explicitly:

这些与将数字乘以你显式地构造的 10 的幂相同:

6.02214 * 10**23

These numbers aren’t Ints or Rats, although you might be able to convert them. They are the more general Num type:

这些数字不是整数有理数,尽管你可能可以转换它们。它们是更通用的Num类型:

put 1e3.^name;  # 1000, but still a Num
put 1e3.Int;    # 1000, but now an Int

Very small numbers have a negative power of 10:

非常小的数字具有10的负数幂:

6.626176e-34

You can use this on not-so-small numbers too:

你也可以在不那么小的数字上使用它:

7.297351e-3

EXERCISE 3.6What is 7.297351e-3 as a fraction? What’s its reciprocal?

练习3.6 7.297351e-3 作为分数是什么?它的倒数是什么?

The Numeric Hierarchy

Raku thinks about numbers relative to their “width.” Int comprises the positive and negative whole numbers and is relatively narrow. The Rat type includes the whole numbers and some of the numbers between them (the ones that can be fractions).

Rat is “wider” not because its endpoints are greater but because there are more numbers between the same endpoints. FatRat is even wider because it pushes the endpoints farther apart to contain even more numbers.

Even wider than the rationals, fat or otherwise, are plain ol’ Nums. Those include the rest of the numbers; the ones that you can’t represent as fractions. We typically call this wider set the Reals but Raku calls them Nums.

And when you think that you are the widest that you can go, the numbers go sideways into the plane of the Complex numbers.

Sometimes you may want to go narrower or wider. Many Raku objects have coercer methods that can do that for you. Start with an Int and turn it into a Complex number. This goes wider:

Raku 考虑数字相对于它们的“宽度”. Int 包含正整数和负数,并且相对较窄。 Rat 类型包括整数和它们之间的一些数字(可以是分数的数字)。

Rat “更宽”并不是因为它的端点更大,而是因为相同端点之间的数字更多。 FatRat 更宽,因为它将端点推得更远,以包含更多数字。

比有理数,fat 或其他的数字更宽的是纯粹的 Num。其中包括其他数字;那些你不能表示为分数的。我们通常称这个更广泛的集合为 Complex,但 Raku 称它们为 Num

当你认为自己是最宽泛的时候,数字就会横向进入复数的平面。

有时你可能想要更窄或更宽。许多 Raku 对象都有强转方法,可以为你做到这一点。从 Int 开始并将其转换为复数。这更广泛:

% raku
> 6.Complex
6+0i

Or start with a Complex number and go narrower. This one can also be an Int:

或者从复数开始,然后变窄。这个也可以是 Int

% raku
> (6+0i).Int
6

You can do those coercions because you know something about the numbers. If you want the narrowest type without knowing what it is beforehand, you can use .narrow. If you tried to convert π to an Int you wouldn’t get an error, but you wouldn’t get π. If you use .narrow you get a Num, the narrowest you can go:

你可以做那些强转,因为你对数字有所了解。如果你想要最窄的类型而不知道它是什么,你可以使用 .narrow。如果你尝试将 π 转换为 Int,则不会出现错误,但你不会得到 π。如果你使用 .narrow 你得到一个 Num,你可以去的最窄的:

% raku
> (π+0i).Int
3
> (π+0i).Int == π
False
> (π+0i).narrow.^name
Num
> (π+0i).narrow == π
True

有时你不能变得更窄:

% raku
> (6+3i).narrow.^name
Complex

EXERCISE 3.7Modify the number-guessing program from the previous chapter so you have to guess a complex number. You have to decide high and low in two directions this time.

练习3.7 修改前一章中的数字猜测程序,这样你就必须猜出一个复数。这次你必须决定两个方向的高低。

总结

That’s most of the story with numbers. You saw some of the methods you can use, and you’ll find even more in the documentation for each type. You also saw a bit about constraining variables to only the type you want, and you’ll become more sophisticated with that.

这是数字的大部分故事。你看到了一些可以使用的方法,你可以在每种类型的文档中找到更多。你还看到了一些关于将变量限制为你想要的类型的信息,并且你将变得更加复杂。

comments powered by Disqus