06 Windows批處理之整數和浮點資料型別

公子奇的博客發表於2024-09-06

在前一篇中,我們詳細介紹了字串和布林資料型別。在本文中,我將重點討論數值資料型別,特別是整數和浮點資料型別,詳細研究它們。批處理可以輕鬆地處理整數,無論它們是十進位制、十六進位制還是八進位制。

然而,浮點數與布林數類似,因為批處理實際上並不顯式地支援它們作為資料型別。但是,再一次,這種限制為富有想象力的批處理編碼器提供了一個創造性的機會,這正是我們在本文完成之前要做的。

八進位制案例研究

記得在某一年的8月1號,我對批處理還比較陌生之前,但我知道的比很多人都多,所以一位同事帶著一個他一直在努力完成的任務來找我。在批處理程式碼中,他需要在給定當前日期的情況下確定前一天的日期。對於一年中的大多數日子來說,這是非常簡單的,但是當今天是一個月的第一天時,它就變得複雜了。月有不同的長度;元旦帶來了獨特的挑戰;閏年每四年發生一次,除非不是閏年。

這個最初的事件發生在2月,也許是3月,這是一個有趣的小練習,我編寫並測試了它。像任何優秀的程式設計師一樣,我在新年的第一天和最後一天都進行了測試。我還測試了幾個月的第一天,尤其是極端的月份,比如1月和12月。我對3月1日進行了幾個不同年份的測試,這不是因為我是在2月份左右編寫這個程式碼的,而是因為閏年的特殊性。很快,我就交出了程式碼,並轉向了其他專案。

程式碼執行了大約6個月。但在8月1日,情況突然變了。我不記得之後的後果,但我的同事花了大量的時間來追查根本原因。他最終鎖定了我的bat檔案,但不明白為什麼它在那天停止工作。他的老闆什麼也沒聽到——程式碼半年都不能工作,然後就大發雷霆。我的同事一定做了某種改變,破壞了這個過程,他很難找到它。

那次搜尋最終浪費了他半個工作日的時間,但經過一番盡職調查後,他最終把失敗的結果告訴了我。我開啟了執行日誌,找到了試圖查詢08/01之前日期的邏輯的結果,並且……

我抬頭看了看天空,舉起雙手,用誇張的口氣尖叫道:“八進位制!”我稍微修飾一下——但至少對我來說,這是相當難忘的。

執行日誌裡有什麼讓我如此不安?讓我們來找出答案,但在深入研究八進位制之前,我先從整數開始。

整數

我們已經對字母數字值使用了set命令,但是它也可以透過/A選項用於算術。回想一下這樣的語句會發生什麼:

set x=4+5

用x表示的變數設定為文字4+5。

使用/A選項將其轉換為算術命令,因此以下結果將x變數設定為數字9:

set /A x=4+5

/A選項將set命令轉換為執行加法和其他算術運算的方法。這些先前的值顯然是硬編碼為數字的。

一個稍微有趣一點的例子涉及到將變數設定為數值,然後透過set /A命令新增它們,如下所示。

set nbr1=4
set nbr2=5
set /A sum = nbr1 + nbr2
> con echo The sum is %sum%.

透過set /A命令新增兩個數值變數

控制檯輸出為“The sum is 9”。上述演示了/A選項執行了三次set命令。首先也是最明顯的一點是,算術是不加鎖的。其次,等號周圍有空格,在前文中我強調了這樣做的危險性。為了演示,這個命令缺少/A選項

set myVar = X

不會將myVar設定為x。它將具有六個字元名稱的變數myVar設定為後跟空格的兩個字元值x。透過比較,/A選項使set命令的行為更像現代語言的賦值運算子,因為命令中的空格不被視為變數名稱或值的一部分;令人耳目一新的是,它們只是空格。

這三個命令在功能上都是相同的;each設定myVar為7:

set myVar=7
set /A myVar=7
set /A myVar = 7

要在沒有/A選項的情況下獲得所需的結果,等號周圍不能存在空格。然而,對於/A選項,它們可以存在,但它們也不是必需的,這是/A選項的第二個重要區別。

第三個區別是變數nbr1和nbr2沒有被百分號包圍。因此,/A選項允許您在不使用普遍存在的分隔符的情況下解析變數。為了靈活起見,您仍然可以使用百分號和嵌入的空格,也可以不使用,因此這四個語句在邏輯上是等效的:

set /A result = nbr1 + nbr2
set /A result = %nbr1% + %nbr2%
set /A result=nbr1+nbr2
set /A result=%nbr1%+%nbr2%

空格使程式碼更具可讀性,所以我建議不要使用前面程式碼中的最後兩個選項。第一個選項是最乾淨的,但是有些人太習慣在變數周圍加上百分號了,所以第二個選項可能會提供舒適的一致性。

讓我們再看一次程式碼中的set /A命令,但這次是在bat檔案的最開頭執行的:

set /A sum = nbr1 + nbr2
> con echo The sum is %sum%.

寫入控制檯的sum的結果值將為0。因為nbr1和nbr2還沒有定義,所以在數字上下文中使用的未設定變數被認為是零,而在字母數字上下文中使用的未設定變數預設為null。由於兩者都未設定,因此算術運算0 + 0的結果為0。

警告:

允許的整數範圍包括-2,147,483,648到2,147,483,647(包含)。批處理將數字儲存為32位帶符號欄位,因此任何整數都將採用這232個值中的一個。這很少會造成問題,但是因為程式碼沒有被編譯,所以要注意確保正在處理的資料符合限制。程式碼不會中止,也不會掛起;它只是無法計算出正確的值。批處理不是宏觀經濟學的首選語言。

批處理演算法

批處理算術不僅僅是簡單的加法。下面的清單顯示了五種主要的算術運算(加、減、乘、除和模除)及其語法:

set /A sum = nbr1 + nbr2
set /A difference = nbr1 - nbr2
set /A product = nbr1 * nbr2
set /A quotient = nbr1 / nbr2
set /A modulo = nbr1 %% nbr2

這些運算子類似於其他程式語言中的運算子,但請注意模除法的雙百分號。help命令顯示一個%符號,但是正確的批處理語法需要兩個。(實際上,模字元只是一個單獨的百分號,但第一個百分號實際上是轉義第二個。如果現在還不太明白,可以把這個想法留到後面的文章中介紹,但現在使用兩個字元。)

現在讓我們執行這些算術命令,但首先我們要定義兩個運算元,nbr1和nbr2。結果以註釋的形式顯示在每個語句的右側(如前所述,與符號分隔了兩個命令,第二個命令可以是rem命令):

setlocal EnableDelayedExpansion

set nbr1=7
set nbr2=2

set /A sum = nbr1 + nbr2           &rem sum=9
set /A difference = nbr1 - nbr2    &rem difference=5
set /A product = nbr1 * nbr2       &rem product=14
set /A quotient = nbr1 / nbr2      &rem quotient=3
set /A modulo = nbr1 %% nbr2       &rem modulo=1

加法、減法和乘法運算不會產生意外結果,但7除以2返回3而不是3.5,因為批處理演算法只處理整數,並截斷結果的小數部分。19除以10不會得到1.9,它甚至不會返回2的四捨五入值。1.9的中間結果被截斷為1。取模是一個返回餘數的有用運算子。對n取模返回0到n - 1的值,所以對偶數取模運算返回0,因為2/2、4/2、6/2等等都是整數,不會產生餘數。奇數返回1,因為3/2、5/2、7/2等等餘數都是1。

奇怪的是,批處理不支援指數函式或冪函式,這對一些人來說是沮喪的,但對另一些人來說是創造力的動力。你可以建立一個案例,它接受一個基數和一個指數,並返回指數結果。

擴充套件的賦值運算子

當您希望向變數新增數字並將結果儲存在同一變數中時,擴充套件的賦值運算子可以簡化程式碼。最明顯的例子是一個簡單的計數器,您可能希望在每次執行set命令時將變數增加1,例如:

set /A veryVerboseTallyVariable = veryVerboseTallyVariable + 1

我故意選擇了一個冗長而麻煩的變數名,因為儘管我們編碼人員可能會嘗試,但它們有時幾乎是不可避免的。

下面的語法在邏輯上是相同的,濃縮的,並且更容易理解:

set /A veryVerboseTallyVariable += 1

下一個命令將17新增到一個命名更簡潔的變數中:

set /A nbr += 17

同樣,下面的一組命令分別是減2、乘2、除2和取模2:

set /A nbr -= 2
set /A nbr *= 2
set /A nbr /= 2
set /A nbr %%= 2

同樣,請注意模數除法的雙百分號。許多有經驗的批處理程式設計師不知道批處理中有擴充套件的賦值運算子,錯誤地認為它們只存在於更現代的語言中,但它們確實存在,您應該在適當的時候使用它們。

運算順序

你可以用數學中的運算順序規則做更復雜的算術。您可能在代數課上學過“括號、指數、乘法和除法以及加法和減法”的首字母縮略詞PEMDAS。對於批處理,我們有PMDAS,它更難發音,但正如前面提到的,指數不支援。讓我們舉個例子:

set /A nbr = 3 * (1 + 2) / 4 - 5

首先,1和2加起來等於3,因為它們在括號裡,儘管加法和減法在操作順序上是最後的。乘法和除法具有相同的層次結構,因此直譯器從左到右執行它們。表示式前面的3乘以加法中的3,得到9,然後9除以4,得到2.25。實際上,它被截斷了,所以它就是2。最後,減去5,結果是-3。

這個例子僅用於教學,因為將nbr設定為-3要簡單得多。在實踐中,將使用硬編碼數字和變數的混合。例如:

set /A nbr = ((nbr1 + nbr2) * -10) / 4 

根據PMDAS的規則,這裡的外括號是不必要的,但它們使語句更具可讀性。擴充套件的賦值運算子還可以處理更復雜的表示式。這兩個語句在邏輯上是相同的:

set /A nbr = nbr + (2 * (4 + nbr) - -5)
set /A nbr += 2 * (4 + nbr) - -5

在這兩個命令中,變數nbr都是由一個也包含nbr的數學表示式來遞增的,唯一的區別是第二個命令使用擴充套件賦值運算子。根據操作順序,兩者都對變數加4,將其加倍,然後減去-5。(減-5等於加5。)最終,這個表示式的結果是nbr增加的量。

八進位制和十六進位制算術

批處理支援八進位制和十六進位制算術。這兩種數字系統都更類似於計算機的思維方式,而不是以10為基數,所以對於程式設計師來說,理解並使用它們是很有用的。

十進位制數字系統以10為基數,使用數字0到9。10沒有數字;取而代之的是兩個數字:新的位值從1開始,而個位從0重新開始,因此是10。相比之下,八進位制系統是以8為基數,使用數字0到7。八進位制數7加上1不能得到8,因為8(和9)在八進位制數系統中是沒有意義的字元。相反,八進位制數10(發音為“1 - 0”,因為它不是“10”)相當於十進位制數8。同樣,八進位制11等於十進位制的9,以此類推。

十六進位制的數字系統以16為基數,所以它有與八進位制相反的問題:它需要16個唯一的數字,比大多數人類數字系統中使用的10個數字要多,因為我們已經進化到兩隻手上各有5個數字。從0數到9,我們得到了A、B、C、D、E和F。十六進位制數B等於十進位制數11,十六進位制數F等於十進位制數15,十六進位制數10等於十進位制數16。

批處理可以使用八進位制、十六進位制和/或十進位制輸入執行算術,同時始終以十進位制形式返回答案。十六進位制數前加0x,八進位制數前只加0。因此,這兩個變數分別被賦八進位制和十六進位制值:

set octalNbr=012
set hexadecimalNbr=0xB

不管運算元的基數是十進位制、八進位制還是十六進位制,批處理總是將結果儲存為十進位制。為了演示,首先看這個例子:

set decimal7=7
set decimal1=1
set octal7=07
set octal1=01
set /A decimal = decimal7 + decimal1
set /A octal = octal7 + octal1

數字7和1以十進位制和八進位制的形式相加。十進位制的結果顯然是8。這兩個八進位制數的和是八進位制10(“1 - 0”,而不是十進位制10),但是直譯器立即將該值儲存為十進位制8。在這個例子中,十進位制和八進位制表現相同,但這並不總是正確的。

現在來看看這個例子:

set decimal11=11
set decimal2=2
set octal11=011
set octal2=02

set /A decimal = decimal11 + decimal2
set /A octal = octal11 + octal2
> con echo The decimal sum is %decimal%.
> con echo The octal sum is %octal%.

十進位制加法得到十進位制13,而八進位制加法得到八進位制13(“1 - 3”,而不是十進位制13)。記住,八進位制沒有8和9。八進位制10是十進位制8,在這個例子中八進位制13是十進位制11。因此,在批處理中,11 + 2 = 13,而011 + 02 = 013 = 11,因此顯示結果如下:

The decimal sum is 13.
The octal sum is 11.

直譯器甚至可以處理十進位制和八進位制混合的算術運算。10 + 10的十進位制加法是20,010 + 010的八進位制加法是16。當將一個十進位制數和一個八進位制數相加時,比如10 + 010,批處理會給出正確的結果18。通常,這種型別的算術是偶然完成的,但有時精明的編碼人員會利用這一點,很高興知道這是可能的。

以類似的方式,這些值被視為十六進位制:

set /A hexadecimalNbr = 0xA * 0x14

透過這種乘法運算,0xA等於十進位制數10,而0x14在轉換為十進位制數時比16大4。該語句執行後,變數等於200,即10和20的乘積。

八進位制和十六進位制可以是強大的工具;但是,如果您打算進行十進位制算術,請小心確保沒有前導零。由於十六進位制從0x開始,意外地執行十六進位制算術要困難得多,但由於一個看似無害的前導零而不知不覺地執行八進位制算術則非常容易。

重要:

因為數學無處不在,你會在後續文章中找到包含各種bat檔案算術示例的例子。批處理還具有用於位操作的算術運算子:位和、位或、位異或、邏輯左移和邏輯右移。我將等到後續再討論它們,因為這些運算子使用了一些特殊的字元,這些字元有其他用途,而且許多有經驗的程式設計師從來沒有在編譯過的程式碼中操作過一點,更不用說批處理了。

浮點數

批處理不顯式地處理浮點數——即非整數的有理數。事實上,如果要對這些數字進行廣泛的處理,可以使用比批處理更好的工具。這類似於用鐵鍬挖房子的地基。這是可以做到的,但只有最嚴厲的苦行僧才能做到。如果任務足夠大,可以編寫一些編譯後的程式碼並從bat檔案呼叫它,但是當需要執行一些輕量級浮點運算時,批處理可以處理它,就像您可以使用鏟子在前院種幾個鬱金香球莖一樣。

請記住,所有批處理變數實際上都只是經過美化的字串。我們可以很容易地為兩個變數賦值為浮點值——也就是說,為小數點賦一個點的一些數字。這裡有兩種面額的美元和美分:

set amt1=1.99
set amt2=2.50

如果這些是整數,我們可以簡單地使用set /A命令將它們相加。讓我們試一下,看看會發生什麼:

set /A sum = amt1 + amt2

結果是儲存在總和中的值3,而不是期望的4.49。每個數字的小數部分被完全忽略,結果是整數1和2的總和。

我們需要去掉小數點,做算術,然後恢復小數點。將每個數字乘以100就可以了,但是批處理不允許這樣做。然而,由於浮點值只是一個經過偽裝的字串,我們可以使用前文中描述的語法刪除小數點:

set amt1=%amt1:.=%
set amt2=%amt2:.=%

現在的數量是199和250。這個set /A命令的結果是449

set /A sum = amt1 + amt2

要還原小數,我們不能簡單地除以100——再次強調,這隻適用於整數——但我們可以使用前面文章中更多的字串解析邏輯。使用子字串,下面的set命令將變數重置為三個專案的連線:除了數字的最後兩個位元組之外的所有內容,硬編碼的小數點(或點),以及數字的最後兩個位元組:

set sum=%sum:~0,-2%.%sum:~-2%
> con echo The sum is %sum%.

最後,寫入控制檯的變數被設定為4.49

乘法也是這樣。如果你花499美元買了一臺新電腦,第一年不用付款,利率為19%,一年後你會欠多少錢?利率換算成1.19,但我們必須去掉小數點。找到兩個整數的乘積後,我們透過將小數點插入最後兩個位元組之前來恢復小數點,如下所示

set amt=499
set factor=1.19
set factor=%factor:.=%
set /A product = amt * factor
set product=%product:~0,-2%.%product:~-2%
> con echo The product is %product%.

上述就表示整數和浮點數的乘法

593.81的產品可能會讓你重新考慮投資計劃。

每個程式設計師的目標都想編寫“銀彈”程式碼。不幸的是,之前的產品更多的是棉網而不是凱夫拉,並且有許多batveat需要討論。我們做了幾個假設,如果違反了其中任何一個,程式碼就會失效。加法假設兩個數都有兩位小數;1.9而不是1.90會使結果偏差10倍。除小數點以外的非數字字元會導致問題,值的前導零會觸發八進位制運算。乘法就更復雜了。前面程式碼包含一個整數數量,但如果amt以美元和美分表示,則乘積將得到四位小數,而不是兩位。為了將結果表示為美元和美分,最後兩個位元組應該被截斷——或者更好的是,四捨五入。

我不會在這裡討論這些細微差別,原因很簡單,如果輸入不一致並且需要資料驗證,批處理浮點演算法可能不是最佳解決方案。為所有可能的情況編寫程式碼將是無趣的。重要的是,程式設計師需要選擇最佳的選項。如果所有值的小數位數一致,則只需幾行程式碼就可以完成算術。在極少數情況下,當我在批處理中使用浮點資料型別時,它是用於涉及一致資料的非常特定的任務。

八進位制案例研究,續

那麼,我在那份執行日誌裡到底找到了什麼在千禧年年初的那一年8月的第一天?在bat檔案中,今天的日期格式為CCYYMMDD,例如20050801,它被分解為三個獨立的欄位:

todaysYear = 2005
todaysMonth = 08
todaysDay = 01

如果todaysDay不是01,我們只需從8位數中減去1,然後繼續。但是當它是01時,我們需要做一些額外的算術。考慮到月份的邏輯(並且理解一月份會有一些特殊的邏輯),我們必須減去1來確定前一個月:

set /A month = todaysMonth - 1

當todaysMonth為03時,月份為2;當todaysMonth是07時,月份是6。但是,當todaysMonth為08時,即8月1日,前一個演算法中的月份解析為-1的值。

直譯器看到前導0並將算術處理為八進位制算術。Octal只理解數字0到7,所以當直譯器看到8時,它認為這個字元和“ohkuh”(在Vulcan語言中對應8的數字)一樣陌生,並直接忽略它。最後,set /A命令將表示式剩餘部分的數學結果(-1)分配給month變數。該值最終打破了日期邏輯,我們無法獲得所需的日期7月31日。

該死的八進位制

使用子字串和if命令,我插入了這一行修復,以去除todaysMonth變數值的前導零(如果存在):

if %todaysMonth:~0,1% equ 0  set todaysMonth=%todaysMonth:~1%

這段程式碼在接下來的幾年裡執行良好,即使是在8月和9月的第一天。如果原始程式碼沒有在8月1日執行,那麼如果在9月1日執行,它就會失敗,因為9月用09表示。但是如果程式碼沒有在這兩天執行呢?下一次失敗是什麼時候?在10月1日,這個月將被記為10。直譯器會把它當作小數來處理,程式碼也會按照預期執行。所以,8月1日和9月1日是唯一能夠破解密碼的日期。

注意八進位制。

總結

在本文中,我討論了數字資料型別以及在批處理中如何處理它們。與大多數其他語言不同,批處理變數沒有定義為特定的資料型別。本質上,所有變數都是簡單的字串,但是當字串包含數字時,它可以被視為數字。

加法、減法、乘法、除法甚至模除法都可以相對輕鬆地處理十進位制整數,使用的是您可能在學校裡學過的運算順序規則。也支援八進位制和十六進位制整數,儘管八進位制算術很容易被錯誤地呼叫。從我的個人經驗來看,確保你的十進位制整數沒有任何零字首。擴充套件的賦值運算子提供了一種方便的、未充分利用的整數遞增工具。

批處理中不支援浮點數字資料型別,但是您已經瞭解到,只需做一點工作,就可以對帶有小數點的數字執行一些輕量級算術。

換句話說,我將在下一篇文章中討論檔案移動。批處理的一個非常有用的特性是建立、複製、移動、重新命名和刪除檔案和目錄。

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章