[Raku] 如何將字串轉換成數字

作者:   發佈於:   #rakulang

在 Raku 語中,如果有個字串 $s 內容裝的都是數目字,而我們要將其傳換為整數值或是有理數數值,大致上來說,就是這三種算式:

  1. $s.Int
  2. $s.parse-base(10)
  3. $s + 0

如果不是十進位而是 $b 進位,就只能靠 $s.parse-base($b)

另外 .parse-base 的傳回值的型別可能是 IntRat。視其處理對象而定。要取得特定型別的話就再往後加轉型。例如 $s.parse-base(10).Rat$s.parse-base(10).IntRatInt 基本上是把小數點後直接省略。視需要或可改用 $s.parse-base(10).floor, $s.parse-base(10).ceiling。

以下分幾種使用情境來說明。

十進位

若有字串內容全是 ASCII 數目字的 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 這十個字符的排列組合,那麼有幾種方式可以將其轉成十進位整數。

比方說用 .Int

my Str $s = "42";
say $s.WHAT, $s.raku; #=> (Str)"42"

my Int $n = $s.Int;
say $n.WHAT, $n.raku; #=> (Int)42

或者用 .parse-base(10)

my Str $s = "42";
say $s.WHAT, $s.raku; #=> (Str)"42"

my Int $n = $s.parse-base(10);
say $n.WHAT, $n.raku; #=> (Int)42

或者,也是可以利用 + 算符會將算字自動轉型的特性:

my Str $s = "42";
say $s.WHAT, $s.raku;
#=> (Str)"42"

my Int $n = $s + 0;
say $n.WHAT, $n.raku;
#=> (Int)42

... 但這一招在 $s 的內容會被解析成非整數的其他數字型別時,會出問題:

my Str $s = "42.0";
my Int $n = $s + 0;

執行這兩行會得到如下的錯誤訊息:

Type check failed in assignment to $n; expected Int but got Rat (42.0)

這是由於我們宣告 $n 必為 Int ,而 $str + 0 會算得 Rat 型別。但另一方面,在 $s"42.0" 時,$s.Int 能直接將其轉為 Int 型別的 42 這值,而 $s.parse-base(10) 則是會將其轉成 Rat 型別的 42.0

為了觀察到這種結果,可在變數宣告處將型別去掉,讓 raku 自動判斷:

my $s = "42.0";
my $n = $s.Int;
say $n.WHAT, $n.raku;
#=> (Int)42

my $m = $s.parse-base(10);
say $m.WHAT, $m.raku;
#=> (Rat)42.0

在前例裡 $s 正好是數值上的整數,但若 $s"42.9" 這種並非數值上的整數的話,$s.Int 所做的轉換則相當於是將去小數點後的數字給捨去。

my $s = "42.9";
my $n = $s.Int;
say $n.WHAT, $n.raku;
#=> (Int)42

$s = "-42.9";
$n = $s.Int;
say $n.WHAT, $n.raku;
#=> (Int)-42

如何轉為十六進位數字

若字串 $s 的內容是十六進位整數,且用的是 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f 這十六個字符,那麼 $s.pasre-base(16) 就可以將 $s 轉換為 Int 型別的數值:

my Str $s = "beef";
my Int $n = $s.parse-base(16);
say $n.WHAT, $n.raku;
#=> (Int)48879

但若是對 "beef" 呼叫 .Int 就只會得到錯誤,因為 .Int 是假設其處理對象是十進位數字。

my $n = "beef".Int;

#=>
Earlier failure:
 Cannot convert string to number: base-10 number must begin with valid digits or '.' in '⏏beef' (indicated by ⏏)

也就是說基本上要將十六進位數字字串轉成其數值,基本上就只能靠 .parse-base(16) 了。

這有個好處,就是可以讓解析十六進位有理數表示法:

my Str $s = "a.a";
my Rat $n = $s.parse-base(16);
say $n.WHAT, $n.raku;
#=> (Rat)10.625

附帶一提,由於 Rat 型別能用來處理有有理數,因此也可再換為分數:

my Rat $n = "a.a".parse-base(16);
say $n.numerator, "/", $n.denominator;
#=> 85/8

再附帶一提,通常分子與分母兩數字是要同時取得的,有個名字長度較短的方法可用:.nude

my Rat $n = "a.a".parse-base(16);
say $n.nude.join("/")
#=> 85/8

(這名字取得真... 好?)

八進位

.parse-base(8) 可處正數,及帶負號的負數:

my Str $s = "100";
my Int $n = $s.parse-base(8);
say $n.WHAT, $n.raku;
#=> (Int)64

$s = "-25";
$n = $s.parse-base(8);
say $n.WHAT, $n.raku;
#=> (Int)-21

二進位

.parse-base(2) 可處正數,及帶負號的負數:

my Str $s = "00010001";
my Int $n = $s.parse-base(2).Int;
say $n.WHAT, $n.raku;
#=> (Int)17

$s = "-00010001";
$n = $s.parse-base(2).Int;
say $n.WHAT, $n.raku;
#=> (Int)-17

如果是要處理以第一位元表示正負號 (0 為正 1 為負) 的那種形式,那就得先事先做一次字串處理了:

my Str $s = "10010001";

$s ~~ s/^1/-/;
say $s;
#=> -0010001

my Int $n = $s.parse-base(2).Int;
say $n.WHAT, $n.raku;
#=> (Int)-17

三十六進位

這是 parse-base 所支援的最高進位。所用的數目字符號是 0 ... 9 加上 A ... Z 二十六個英文字母。小寫 a .... z 也是可以用的,與其大寫字母對應到同樣的數值。

my Int $n = "Raku".parse-base(36)
say $n.WHAT, $n.raku;
#=> (Int)1273422