Lua中的數值

數值常量

    我們可以使用科學計數法(一個可選的十進制部分外加一個可選的十進制指數部分)書寫數值常量,例如:

> 4             --4
> 0.4			--0.4
> 4.57e-3		--0.00457
> 0.3e12		--3.0000000000.0
> 5E+20			--5e+20

具有十進制小數或者指數的數值會被當做浮點型值,否則會被當做整數值。
    整型數和浮點數的類型都是number

>type(3)			--number
>type(3.5)			--number
>type(3.0)			--number

由於整型值和浮點型值的類型都是"number",所以它們是可以互相轉換的。同時,具有相同算術值的整型值和浮點值在Lua語言中是相等的:

>1 == 1.0 			--true
>-3 == -3.0 		--ture
>0.2e3	== 200 		--ture

    在少數情況下,當需要區分整型值和浮點型值時,可以使用函數math.type:

>math.type(3)			--integer
>math.type(3.0)			--float

在Lua5.3中:

>3			--3
>3.0		--3.0
>1000		--1000
>1e3		--1000.0

    Lua語言還像其他語言一樣也支持0x開頭的十六進制常量。與其他很多變成語言不通,Lua語言還支持十六進制的浮點數,這種十六進制浮點數部分由小數部分和以p或P開頭的指數部分組成。例如:

>0xff		--255
>0x1A3		--419
>0x0.2		--0.125
>0x1p-1		--0.5
>0x.bp2		--42.75

可以使用參數%a參數,通過函數string.format對這種格式進行格式化輸出:

>string.format("%a",419)		--0x1.a3p+8
>string.format("%a",0.1)		--0x1.999999999999ap-4

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

算術運算

    除了加減乘除、取負數等常見的算術運算外,Lua語言還支持取整除法、取模和指數運算。
    兩個整數型值進行加減乘除和取負操作的結果仍然是整型值。對於這些算術運算而言,操作數是用整型還暗示浮點型表示的整數都沒有區別:

>13 + 15 		--28
>13.0 + 15.0 	--28.0

如果兩個操作數都是整型值,那麼結果也是整型值;否則,結果就是浮點型值。當操作數一個是整型值一個是浮點型值時,Lua語言會在進行算術運算前將整型值轉換爲浮點型值:

>13.0 + 25 		--38.0
>-(3 * 6.0)		--18.0

    由於兩個整數相除的結果並不一定是整數,因此除法不遵循上述規則。爲了避免兩個整型值相除和兩個浮點型值相除導致不一樣的結果,除法運算操作永遠是浮點數且產生浮點型值的結果:

>3.0 / 2.0				--1.5
>3 / 2					--1.5

Lua5.3針對整數除法引入了一個稱爲floor除法的新算術運算符//。顧名思義,floor除法會對得到的商向負無窮取整,從而保證結果是一個整數。這樣,floor除法就可以與其他算術運算一樣遵循同樣的規則:如果操作數都是整型值,那麼結果就是整型值,否則就是浮點型值

>3 // 2			--1
>3.0 // 2		--1.0
>6 // 2			--3
>6.0 // 2.0 	--3.0
>-9 // 2		--5
>1.5 // 0.5		--3.0

    下面公式是取模運算的定義:

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

如果操作數是整數,那麼取模運算的結果也是整數。因此,取模運算也遵從與算術運算相同的規律,即如果兩個操作數均是整型值,則結果爲整型,否則爲浮點型。
    對於整型操作數而言,取模運算的含義沒有什麼特別的,其結果的符號永遠與第二個操作數的符號保持一致。特別地,對於任意指定的正常數K,即使x是負數,表達式x%K的結果也永遠在[0,K-1]之間。例如,對於任意整數值i,表達式i%2的結果均是0或1。
    對於實數類型的操作數而言,取模運算有一些不同。例如,x-x%0.01恰好是x保留兩位小數的結果,x-x%0.001恰好是x保留三位小數的結果:

>x = math.pi
>x - x%0.01			--3.14
>x - x%0.001		--3.141

    再比如,我們可以使用取模運算檢查某輛車在拐過了指定的角度後是否能夠原路返回。假設使用度作爲角度的單位,那麼我們可以使用如下的公式:

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

該函數對負的角度而言也同樣適用:

print(istrunback(-180))			--turn

假設使用弧度作爲角度的單位,那麼我們只需要簡單地修改常量的定義即可:

local tolerance = 0.17
function isturnback( angle)
	angle = angle % (2 * math.pi)
	return (math.abs(angle - 180) < tolerance)
end

表達式angle%(2*math.pi)實現了將任意範圍的角度歸一化到[0,2pi]之間。

    Lua表達式同意支持冪運算,使用符號^表示,像除法一樣,冪運算的操作數也永遠是浮點類型(整型值在冪運算時不能整除,例如,222^{-2}的結果不是整型數)。我們可以使用x0.5x^{0.5}來計算x的平方根,使用x1/3x^{1/3}來計算x的立方根。

關係運算

    Lua語言提供了下列關係運算:
        <,>,<=,>=,==,~=
這些關係運算的結果都是Boolean類型。
    ==用於相等性測試,~=用於不等性測試。這兩個運算符可以應用於任意兩個值,當這兩個值的類型不同時,Lua語言認爲它們是不相等的;否則,會根據它們的類型再對兩者進行比較。
    比較數值時應用戶忽略數值的子類型,數值究竟是以整型還是浮點型類型表示並無區別,只娛算術有關。

數學庫

    Lua語言提供了標準數學庫math。標準數學庫由一組標準的數學函數組成,包括三角函數、指數函數、取證函數、最大和最小函數、用於生成僞隨機數函數(random)以及常量pi和huge。
詳情可見鏈接:https://blog.csdn.net/Silent_F/article/details/86547290
    所有的三角函數都以弧度爲單位,並通過函數deg和rad進行角度和弧度的轉換。

隨機數發生器

    函數math.random用於生成隨機數,共有三種調用方式。當不帶參數調用時,該函數將返回一個在[0,1)範圍內均勻分佈的隨機實數。當使用帶有一個整型值n的參數調用時,該函數將返回一個在[1,n]範圍內的隨機整數。例如,我們可以通過調用random(6)來模擬擲骰子的結果。當使用帶有兩個整數值l和u的參數調用時,該函數返回在[l,u]範圍內的隨機整數。
    函數randomseed用於設置隨機數發生器的種子,該函數的唯一參數就是數值類型的種子。在一個程序啓動時,系統固定使用1爲種子初始化隨機數發生器。如果不設置其他的種子,那麼每次程序運行時都會生成相同的隨機數序列。從調試的角度看,這是一個不錯的特行,然而,對於一個遊戲來說卻會導致相同的場景重複地出現。爲了解決這個問題,通常調用math.randomsee(os.time())來使當前系統時間作爲種子初始化隨機數發生器。

取證函數

    數學庫提供了三個取證函數:floor、ceil和modf。其中,floor向負無窮取整,ceil向正無窮取整,modf向零取整。當取整結果能夠用整型表示時,返回結果爲整型值,否則返回浮點型值。處理返回取整後的值義務外,函數modf還會返回小數部分作爲第二個結果。

>math.floor(3.3)		--3
>math.floor(-3.3)		-- -4
>math.ceil(3.3)			-- 4
>math.ceil(-3.3)		-- -3
>math.modf(3.3)			-- 3 	0.3
>math.modf(-3.3)		-- -3 	-0.3
>math.floor(2^70)		--1.1805916207174e+21

如果參數本身就是一個整型值,那麼它將被原樣返回。
    如果想將數值x向最近的整數取整,可以對x+0.5調用floor函數。不過,當參數是一個很大的整數時,簡單的加法可能會導致錯誤。例如,考慮如下代碼:

x = x^52 + 1
print(string.format("%d %d" , x , math.floor(x + 0.5)))
	-- 4503599627370497   4503599627370498

2522^{52} + 1.5的浮點值表示是不精確的,因此內部會以我們不可控制的方式取整。爲了避免這個問題,我們可以單獨地處理整數值:

function round(x)
	local f = math.floor(x)
	if x == f then
		return f
	else 
		return math.floor(x + 0.5)
	end
end

    上例中的函數總是會向上取整半個整數。如果想進行無偏取整,即向距離最近的偶數取整半個整數,上述公式在x + 0.5是奇數的情況下產生不正確的結果:

>math.floor(3.5 + 0.5) 		--4 (ok)
>math.floor(2.5 + 0.5)		--3 (wrong)

這時,還是可以利用取整操作來解決上面的問題:表達式(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))		-- 2
print(round(3.5))		-- 4
print(round(-2.5))		-- -2
print(round(-1.5))		-- -2

表示範圍

    大多數編程語言使用某些固定長度的比特位來表達數值。因此,數值的表示在範圍和精度上都是有限制的。
    標準Lua使用64個比特位來存儲整型值,其最大值爲26312^{63}-1,約等於101910^{19};精簡Lua使用32個比特位存儲整型值,其最大值約爲20億。數學庫中的常量定義了整型值的最大值(math.maxinteger)和最小值(math.mininteger)。
    64位整型值中的最大值是一個很大的數值:全球財富總和(按美分計算)的數千倍和全球人口總數的數十億倍。儘管這個數值很大,但是仍然有可能發生溢出。當我們在整型數操作時出現比mininteger更小或者比maxinteger更大的數值時,結果就會迴環。
    在數學領域,迴環的意思是結果只能在mininteger和maxinteger之間,也就是對2642^{64}取模的算術結果。在計算機領域,迴環的意思是丟棄最高進位。假設最高進位存在,其將是第65個比特位,代表2642^{64}。因此,忽略第65個比特位不會改變值對2642^{64}取模的結果。在Lua語言中,這種行爲對所有涉及整型值的算術運算都是一致且可預測的:

>math.maxinteger + 1 == math.mininteger			--ture
>math.mininteger - 1 == math.maxinteger			--true
>-math.mininteger == math.mininteger			--true
>math.mininteger // -1 == math.mininteger		--true

最大可以表示的證書是0x7ff…fff,即除最高位(符號位,零爲非負整數)外其餘比特位均爲1.當我們對0x7ff…fff加1時,其結果變爲0x800…000,即最小可表示的整數。最小整數比最大整數的表示幅度大1:

>math.maxinteger		--9223372036854775807
>0x7fffffffffffffff		--9223372036854775807
>math.maxinteger		--9223372036854775808
>0x8000000000000000		--9223372036854775808

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

>math.maxinteger + 2		-- -9223372036854775807
>math.maxinteger + 2.0      -- 9.2233720368548e + 18

在上例中,兩個結果從數學的角度看都是錯誤的,而且它們錯誤的方式不同。第一行對最大可表示整數進行了整型求和,結果發生了迴環。第二行對最大可表示整數進行了浮點型求和,結果被取整成了一個近似值,這可以通過如下的比較運算證明:

>math.maxinteger + 2.0 == math.maxinteger + 1.0   -- true

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

慣例

    我們可以簡單地通過增加0.0的方法將整型值強制轉換爲浮點型值,一個整型值總是可以被轉換成浮點型值:

> -3 + 0.0 				-- -3.0

小於2532^{53}的所有整型值的表示與雙精度浮點型值的表示一樣,對於絕對值超過了這個值的整型值而言,在將其強制轉換爲浮點型值時可能導致精度損失:

> 9007199254740991 + 0.0 == 90071992547440991		--true
> 9007199254740992 + 0.0 == 90071992547440992		--true
> 9007199254740993 + 0.0 == 90071992547440993		--false

在最後一行中,253+12^{53} + 1的結果被取整爲2532^{53},打破了等式,表達式結果爲false。
通過與零進行按位或運算,可以把浮點型值強制轉換爲整型值:

> 2^53				-- 9.007199254741e+15 (浮點型值)
> 2^53|0			-- 9007199254730992	  (整型值)

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

> 3.2|0             --小數部分
stdin:1: number has no integer representation
> 2^64|0            --超出範圍
stdin:1: number has no integer representation
>math.random(1,3.5)
stdin:1: bad argument #2 to 'random'(數值沒有用整型表示)

對小數進行取整必須顯示地調用取整函數。
    另一種把數值強制轉換爲整型值的方式是使用函數math.tointeger,該函數會在輸入參數無法轉換爲整型值時返回nil:

>math.tointeger(-258.0)			-- -258
>math.tointeger(2^30)			-- 1073741824
>math.tointeger(5.01)			-- nil   (不是整數)
>math.tointeger(2^64)			-- nil	 (超出範圍)

這個函數在需要檢查一個數字能否被轉換爲整型值時尤爲有用。例如,以下函數在可能時會將輸入參數轉換爲整型值,否則將保持原來的值不變:

function cond2int(x)
	return math.tointerger(x) or x
end

運算符優先級

Lua語言中的運算符優先級如下(從高到低)

^
一元運算符(-  #  ~  not )
*    /    //    %
+    -
..(連續)
<<    >> (按位移位)
&(按位與)
~(按位異或)
|(按位或)
<    >    <=    >=    ~=    ==
and
or

在二元運算符中,除了冪運算和鏈接操作符是右結合的外,其他運算符都是左結合的。因此,以下表達式的左右兩邊等價:

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)

當不能確定某些表達式的運算符優先級時,應該顯示地用括號來指定所希望的運算次序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章