Lua:04---number數值型別:interger、float、算術運算/關係運算/運算子優先順序、math數學庫、interger與float的轉換

2020-08-11 16:20:26

一、Lua數值型別發展

  • 在Lua 5.2及之前的版本中,所有的數值都以雙精度浮點格式表示
  • 從Lua 5.3版本開始,Lua語言爲數值格式提供了兩種選擇:
    • 整型值:稱爲interger的64位元整型
    • 浮點型值:稱爲float的雙精度浮點型別

二、數值常數表示

  • 下面 下麪是一些合理的數值常數表示:
4

0.4

  • 其中還可以使用科學記數法:
    • 格式爲一個可選的十進制部分外加一個可選的十進制指數部分)
    • 科學記數法最終是一個浮點數
4.57e-3

0.3e12

5E+20

三、型別判斷

type()函數

  • 使用type()函數可以獲取整型值和浮點型值的型別,返回的都是number(表示數值型別)
type(3)

type(3.5)

type(3.14e3)

math.type()函數

  • 如果想要區分整型值和浮點型值,可以使用這個函數
math.type(3)

math.type(3.5)

math.type(3.14e3)

四、十六進制表示

  • Lua也支援以0x開頭的十六進制常數
  • Lua不僅支援十六進制的整型、還支援十六進制的浮點數
  • 例如:
0xff

0x1A3

0x0.2

  • 十六進制浮點數還可以由小數部分和以p或P開頭的指數部分組成(Lua 5.2引入的)例如:
0x1p-1

0xa.bp2

  • 通過string.format()的%a參數可以對上面的這種格式進行格式化的輸出:
string.format("%a", 419)

string.format("%a", 0.1)

  • 雖然這種格式很難閱讀,但是這種格式可以保留所有浮點數的精度,並且比十進制的轉換速度更快

五、算術運算

  • Lua支援的算術運算有:
    • 加(+)、減(-)、乘(*)、除(/)
    • 取負數(-)
    • 取整除法/floor除法(//)
    • 取模(%)
    • 指數運算
    • 冪運算(^)
  • Lua 5.3引入整型的主要建議是:開發人員要麼選擇忽略整型和浮點型二者之間的不同,要麼就完整地控制每一個數值的表示。因此,所有的算術操作符不論操作整型值還是浮點型值,結果都是一樣的

整型值和浮點型值之間的算術運算

  • 如果兩個運算元都是整型值,則結果也是整型值;否則就是浮點型值
  • 當兩個運算元的型別不同時,運算之前會先將整型值轉換爲浮點型值
  • 例如:
13 + 15

13.0 + 15

13 + 15.0

13.0 + 15.0

1 * 2

1 * 2.0

除法的注意事項

  • 由於兩個整數相處並不一定是整數,因此當兩個數進行相除時,interger都會轉換爲浮點數(即使兩個運算元都是整數液轉換)
  • 並且除法運算的結果也是浮點數
  • 例如:
3.0 / 2.0

3 / 2

floor除法

  • floor除法會對得到的商向負無窮取整,從而保證結果是一個整數
  • 這樣,floor除法就可以與其他算術運算一樣遵循同樣的規則:如果運算元都是整型值,那麼結果就是整型值,否則就是浮點數型別
  • 例如:
3 // 2

3.0 // 2

6 // 2

6.0 // 2.0

-9 // 2

1.5 // 0.5

取負數運算

  • 返回一個值的負數
  • 使用的時候注意,對要操作的表達式的結果要加上括號,否則有點像是定義一個負數的感覺
  • 例如:
-1.0

-1

-(-1)

-- 這個是註釋, 而不是兩次取負
--1

- (3 * 6.0)

取模運算

  • 取模運算的結果型別與上面介紹的一樣,如果兩個運算元都是整型值則返回整型;否則返回浮點數
  • 例如:
a = 10

b = 2

a % b == a - ((a // b) * b)

  • 對於實數型別(浮點數)而言,取模運算有一些不同,例如x-x%0.01恰好是x保留2位小數的結果,x-x%0.001恰好是x保留3位小數的結果
x = math.pi

x - x%0.01

x - x%0.001

  • 演示案例:我們可以使用取模運算檢查某輛車在拐過指定的角度後是否能夠原路返回。假設使用度作爲角度的單位,那麼我們可以使用下面 下麪的函數
local tolerance = 10

function isturnback(angle)
    angle = angle % 360
    return (math.abs(angle - 180) < tolerance)
end

print(isturnback(-180))

print(isturnback(90))

  • 使用弧度作爲角度的單位,那麼只需要簡單的修改一下常數的定義就可以了
local tolerance = 0.17

function isturnback(angle)
    -- 這一條語句實現了將任意範圍的角度歸一化到[0,2π)之間
    angle = angle % (2*math.pi)
    return (math.abs(angle - math.pi) < tolerance)
end

print(isturnback(-180))

print(isturnback(90))

冪運算

  • Lua也支援冪運算,使用符號^表示
  • 像除法一樣,冪運算的運算元和結果也永遠是浮點型別(整型型別在冪運算時不能整除,例如,2^{-2}的結果不是整型值)
  • 我們可以使用x^0.5來計算x的平方根,使用x^(1/3)來計算x的立方根
x = 4

x ^ 0.5

x = 27

x ^ (1 / 3)

六、關係運算

  • Lua支援的關係運算符如下:
    • 大於(>)、小於(<)
    • 大於等於(>=)、小於等於(<=)
    • 相等(==)
    • 不相等(~=)
  • Lua關係運算的結果都是boolean型別
  • ==、~=說明:這兩個運算子可以應用於任意兩個值,當這兩個值的型別不同時,Lua語言認爲它們是不相等的;否則,會根據它們的型別再對兩者進行比較
1 == 2

1 == 1

-- 型別不同, 直接返回false, 根本不進行比較
1 == "1"

"1" == "1"

"1" == "2"

  • 比較數值時永遠忽略數值的子型別,數值究竟是以整型還是浮點型表示並無區別,只與算術值有關(儘管如此,比較具有相同子型別的數值時效率更高)
1.0 == 1

1 == 1

1.1 == 1

七、數學庫

  • Lua語言提供了標準數學庫math,由一組標準的數學函陣列成,包括:
    • 三角函數(sin、cos、tan、asin等):所有三角函數都以弧度爲單位,並通過函數deg和rad進行角度和弧度的轉換
    • 指數函數
    • 取整函數
    • 最大函數max、最小函數min
    • 用於生成僞隨機的僞隨機函數(random)
    • 常數pi
    • 常數huge(最大可表示數值,大多大叔平臺上代表inf)

亂數發生器

  • 函數math.random()用於生成僞亂數
  • 一共有3中呼叫方式:
    • 不帶參數呼叫時:函數返回一個在[0,1)範圍內的均勻分佈的僞隨機實數
    • 當使用帶有一個整型值n的參數呼叫時:函數返回一個在[1, n]範圍內的僞隨機整數
    • 使用兩個整型值l和u的參數呼叫時:函數返回在[l, u]範圍內的僞隨機整數
  • 例如:
math.random()

-- 可以模擬擲骰子的結果
math.random(6)

math.random(10, 50)

  • 函數randomseed()用於設定僞亂數發生器的種子,該函數的唯一參數就是數值型別的種子
    • 在一個程式啓動時,系統固定使用1爲種子初始化僞隨機發生器;如果不設定其他的種子,那麼每次程式執行時都會生成相同的僞亂數序列
    • 從偵錯的角度看,這是一個不錯的特性,然而,對於一個遊戲來說卻會導致相同的場景重複不斷的出現。爲了解決這個問題,通常呼叫math.randomseed(os.time())來使用當前系統時間作爲種子初始化亂數發生器(os.time()在後面文章介紹)

取整函數

  • 數學庫提供了三個取整函數:
    • floor:向負無窮取整
    • ceil:向正無窮取整
    • modf:向零取整
  • 當取整的結果能用整數表示時,返回結果爲整型值,否則返回浮點型值
  • modf除了返回取整後的值之外,還會返回小數部分作爲第二個結果(Lua支援一個函數返回多個值)
  • 例如:
math.floor(3.3)
math.floor(-3.3)

math.ceil(3.3)
math.ceil(-3.3)

math.modf(3.3)
math.modf(-3.3)

math.floor(2^70)

  • 如果參數本身是一個整型值,那麼它將原樣返回
math.floor(1)
math.floor(-1)
math.floor(0)

math.ceil(1)
math.ceil(-1)
math.ceil(0)

math.modf(1)
math.modf(-1)
math.modf(0)

  • 如果想將數值x向最近的整數取整,可以對x+0.5呼叫floor函數
  • 不過,當參數是一個很大的整數時,簡單的加法可能導致錯誤,考慮下面 下麪的程式碼
    • 2^{52}+1.5的浮點值表示是不精確的,因爲內部會以我們不可控制的方式取整
x = 2^52 + 1

print(string.format("%d %d", x, math.floor(x + 0.5)))

  • 爲了避免上面出現的問題,可以單獨地處理整數值。例如:
function round(x)
    local f = math.floor(x)
    if x == f then return f
    else return math.floor(x + 0.5)
    end
end
  • 上面的函數總是會向上取整半個證書(例如2.5會被取整爲3)。如果想進行無偏取整,即向距離最近的偶數取整半個整數,上述公佈在x+0.5是奇數的情況下會產生不正確的結果
-- ok
math.floor(3.5 + 0.5)

-- wrong
math.floor(2.5 + 0.5)

  • 這時,還可以利用取整操作來解決上述公式中存在的問題:表達式(x%2.0==0.5)只有在x+0.5爲奇數時(也就是我們的公式會出錯的情況)爲真。基於這些情況,定義一個無偏取整函數就很簡單了:
function round(x)
    local f = math.floor(x)
    if(x == f) or (x % 2.0 == 0.5) then
        return f
    else
        return math.floor(x + 0.5)
    end
end

print(round(2.5))
print(round(3.5))
print(round(-2.5))
print(round(-1.5))

八、數值型別取值範圍

整型取值範圍

  • 標準Lua使用64個位元位來儲存整型值,其最大值爲2^{63}-1,約等於10^{19}
  • 數學庫中的math.maxinteger和math.mininteger常數分別定義了整型值的最大值和最小值

  • 迴環:當數值很大或者很小發生溢位時,就會發生迴環。迴環的意思就是結果只能在maxinteger和mininteger之間,也就是對2^{64}取模的算術結果。例如:

math.maxinteger + 1 == math.mininteger

math.mininteger - 1 == math.maxinteger

-math.mininteger == math.mininteger

math.mininteger // -1 == math.mininteger

  • 最大可以表示的整數是0x7fffffffffffffff(15個f),即處最高位(符號位,0位非負數值)外其餘位元位均爲1。當我們對0x7fffffffffffffff加1時,其結果就變爲0x8000000000000000,即最小可表示的整數。最小整數比最大整數的表示幅度加1
math.maxinteger

0x7fffffffffffffff

math.mininteger

0x8000000000000000

浮點型別取值範圍

  • 對於浮點數而言,標準Lua使用雙精度。標準Lua使用64個位元位表示所有數值,其中11位爲指數。雙精度浮點數可以表示具有大致16個有效十進制位的數,範圍從-10^{308}10^{308}
  • 雙精度浮點數對於大多數實際應用而言是足夠大的,但是我們必須瞭解精度的限制。如果我們使用十位表示一個數,那麼1/7會被取整到0.142857142。如果我們使用十位計算1/7*7,結果會是0.999999994而不是1。此外,用十進制表示的有限小數在用二進制表示時可能是無限小數。例如,12.7-20+7.3即便使用雙精度表示也不是0,這是由於12.7和7.3的二進制表示不是有限小數
  • 由於整型值和浮點型值的表示範圍不同,因此當超過它們的表示範圍時,整型值和浮點型值的算術運算會產生不同的結果:
math.maxinteger + 2

math.maxinteger + 2.0

  • 上面的結果分析:
    • 第一行對最大可表示整數進行了整型求和,結果發生了迴環
    • 第二行對最大可表示整數進行了浮點型求和,結果被取整成了一個近似值。這可以通過下面 下麪的比較運算證明
math.maxinteger + 2.0 == math.maxinteger + 1.0

  • 儘管每一種表示方法都有其優勢,但是隻有浮點型才能 纔能表示小數。浮點型的值可以表示很大的範圍,但是浮點型能夠表示的整數範圍被精確地限制在[-2^{53}, 2^{53}]之間(不過這個範圍已經很大了)。在這個範圍內,我們基本可以忽略整型和浮點型的區別;超出這個範圍後,我們則應該謹慎地思考所使用的表示方式

九、運算子優先順序

  • Lua中運算子的優先順序如下所示(從高到低):

  • 在二元運算子中,除了冪運算和連線操作是右結合的外,其他運算子都是左結合的。因此,下面 下麪各個表達式的左右兩邊等價
a + i < b/2+1     -- 等價於 (a+i) < ((b/2)+1)

5+x^2*8           -- 等價於 5+((x^2)*8)

a < y and y <= z  -- 等價於 (a<y) and (y<=z)

-x^2              -- 等價於-(x^2)

x^y^z             -- 等價於 x^(y^z)
  • 當不確定某些表達式的運算子優先順序時,應該顯式地用括號來指定所希望的運算次序

十、整型值與浮點型值之間的轉換

整數轉浮點數

  • 我們可以通過將整型值加上0.0將其轉換爲浮點型:
-3 + 0.0

0x7fffffffffffffff + 0.0

  • 小於2^{53}(即9007199254740992)的所有整型值的表示與雙精度浮點型值的表示一樣,對於絕對值超過了這個值的整型值而言,在將其強制轉換爲浮點型值時可能導致精度損失
9007199254740991 + 0.0 == 9007199254740991

9007199254740992 + 0.0 == 9007199254740992


-- 9007199254740992 + 1被取整爲9007199254740992,因此不相等
9007199254740993 + 0.0 == 9007199254740993

浮點數轉整數

  • 通過與0進行按位元或運算,可以把浮點型值轉換爲整型值
-- 浮點型值
2^53

-- 整型值
2^53 | 0

  • 在將浮點數強制轉換爲整數時,Lua會檢查數值是否與整型值表示完全一致(即沒有小數部分且其值在整型值的表示範圍內)。如果不滿足條件則會拋出異常。例如
-- 有小數部分
3.2 | 0

-- 超出範圍
2^64 | 0

-- 數值沒有用整型表示
math.random(1, 3.5)

  • 對小數取整必須顯式地呼叫取整函數
  • 另一種把浮點數轉換爲整型值的方式是使用math.tointeger(),該函數會在輸入參數無法轉換爲整型值時返回nil
math.tointeger(-258.0)

math.tointeger(2^30)

-- 不是整數值
math.tointeger(5.01)

-- 超出範圍
math.tointeger(2^64)

  • 這個函數在需要檢查一個數字能夠被轉換成整型值時尤其有用。例如,以下函數在可能時會將輸入參數轉換爲整型值,否則保持原來的值不變:
function cond2int(x)
    return math.tointeger(x) or x
end

十一、相容性

  • 請參閱《Lua程式設計》P27