為什麼“含頭不含尾”是科學的
什麼是美?
在理工科領域,簡單就是美。計算機軟體領域也是一樣。簡單意味著易理解,不容易出Bug。
從0開始的陣列下標
在計算機程式設計中陣列的下標往往是從0開始,而老百姓熟悉的是從1開始的數字。按道理從1開始更自然更容易接受,也就意味著簡單,可為什麼多數的程式語言的陣列是從零開始的呢?這個可不僅僅是習慣和語言設計者的個人的喜好的問題。
一句話,從0開始能夠在許多方面帶來運算的簡單化。比如一維陣列和二維陣列的換算。如果我們規定下標,都從1開始,那麼一維陣列的下標就會是從1到9對應的二維陣列的座標就是(1,1)到(3,3)。兩個陣列的座標對應如下。
而一維陣列變成二維陣列的座標轉換公式如下:
x = (i - 1) \ 3 + 1
y = (i - 1) % 3 + 1 = i % 3
(這裡,\代表整除,即去掉商的小數部分。%代表取模,即得到餘數。下同)
二維轉一維的公式如下:
i = (x - 1) * 3 + (y - 1) + 1 = (x - 1) * 3 + y
但要是我們規定下標從0開始,對應的轉換公式如下:
x = i \ 3
y = i % 3
i = x * 3 + y
倘若你覺得多出來幾個+1/-1不算太麻煩,那麼請看看一維和三維的轉換
從1開始的公式:
x = (i - 1) \ 9 + 1
y = ((i - 1) % 9) \ 3 + 1
z = (i - 1) % 3 + 1
i = (x - 1) * 9 + (y - 1) * 3 + z
從0開始的公式:
x = i \ 9
y = (i % 9) \ 3
z = i % 3
i = x * 9 + y * 3 + z
不難發現,從0開始的座標轉換公式變得簡單。同樣的結論也適用於更高維的轉換。
時間段的表示
在程式設計中,經常遇到要表示一段(連續)時間。比如某幾天或者某個月,或者某幾個月。通常的做法就是用兩個時間點來表示這段時間,即是用這段時間的開始時間和結束時間來代表這一段時間。但同時也產生了“該不該包含開始時間點,該不該包含結束時間點”這樣的分歧。比如2018年1月1日到2018年2月1日。有沒有包含1月1日這一整天呢,這個大家還是有共識的,就是有。那麼有沒有包含2月1日這一天呢?這可不一定。
有一種觀點是這樣的,既然是到2月1日,那當然是包括2月1號這一天,如果不想包含這一天,那麼應該是到1月31日。所以無論從介面上選擇起始時間還是資料的傳輸儲存都是包含最後的這一天的意思。我們可以稱這種方式為“含頭含尾”。
我們知道1.0等於1,1.00也等於1。同理,2018年1月1日等於2018年1月1日0:00:00。2018年2月1日等於2018年2月1日0:00:00。按含頭含尾,當用這兩個時間點來表示一段時間時。如果時間的單位是天,表示的是1月份這一整個月,外加2月1號這一整天。如果時間的單位是小時,表示的是1月份一整個月,加2月份的頭1個小時。以此類推,也有可能表示的是加2月份的一分鐘,或者可能是包含2月份的整個月。到底是取2月份的多長時間需要額外約定。傳送資料時,一般只傳兩個時間點,而不傳這個“額外的約定”,傳送和接收雙方一般是通過具體業務的理解推測這個單位,並且認為這是顯而易見的,雙方不容易發現對方與自己推測的不同,這導致埋下了一個可能產生Bug的坑。
假如我們規定表示的時間段包含開始的時間點,不包含結尾的時間點,簡稱為“含頭不含尾”。那麼很明確前面兩個時間點表示的是完整的1月份。假如還要包含2月份的第一天,那麼結尾的時間點就是2月2日。假如要包含2月份的第一個小時,那麼結尾的時間點是2月1日 1:00。如果要包含2月份的整個月,那麼結尾的時間點是3月1日。前面說過不寫時分秒就等同於0時0分0秒。因為含頭不含尾,所以並不包含結尾的這一秒在內。
由此可見,含頭不含尾的表示方式,可以精確地表示任意的一個時間段,雙方不需要額外約定時間的單位,做到了與具體的業務無關。一個方法可以同時處理不同的時間單位。這就是簡單,這就是美。