踩坑了!0作為除數,不一定會丟擲異常!

2022-08-29 15:03:18

你好呀,我是歪歪。

踩坑了啊,又踩坑了啊!

這次踩到一個特別無語的常識坑。知道真相的那一刻,人就是整個麻掉。

先上個程式碼:

private static double calculate(double a, int b) {
    return a / b;
}

你先別問為什麼計算不用 BigDecimal,反正程式裡面就是有一個類似於這樣的方法。

正常用起來也沒啥毛病:

注意,我說的是「正常使用看起來沒毛病」,不正常使用是怎麼樣的呢?

看到輸出結果是 「Infinity」 的時候,我甚至揉了一下眼睛,以為自己是過於熱愛工作,導致用眼過度,看花了。

有一說一,我真沒見過這玩意。但是這個單詞我認識啊:

在我有限的認知裡面, 0 是不可以作為除數的,如果作為除數會丟擲異常才對。

但是這個簡單的案例打破了我的認知,它不僅沒有丟擲異常,還給我了一個「無窮大的數」。

在一臉懵逼中,我知道,素材這不就來了嘛。

搜尋一番

如果是在使用框架的過程中遇到問題,一般來說我是先自己偵錯一下,掙扎一波,看看是不是自己的開啟方式不對。

但是這個問題太簡單了,以至於我甚至找不到偵錯的角度。

怎麼辦?

只有直接拿出程式設計師的祖傳技能了:面向瀏覽器程式設計。

於是我輸入搜尋鍵碼 「Java Infinity」,排在第一的是某部落格網站:

我個人是不太喜歡這個網站,所以我按照個人習慣重新搜尋了一次:

找到了下面這個連結:

https://www.cnblogs.com/zhisuoyu/p/5314541.html

從這篇文章中我知道了,原來在我的認知裡面,0 作為除數會丟擲下面這個異常,還有一個前提是「整型運算」:

java.lang.ArithmeticException: / by zero

在 Double 和 Float 裡面都定義了「正無窮」和「負無窮」這兩個常數:

現在我知道在浮點運算的時候,0 是可以作為除數的。

但是,為什麼呢?

Java 裡面什麼這樣設計呢,為什麼不一視同仁呢?

部落格里面沒有寫,但是我知道要找到這個問題的答案,這個地方可以去看看:

https://stackoverflow.com/

於是我用 「Java Double Float Infinity」 關鍵字去查詢了一下:

很容易就找到了這個連結:

https://stackoverflow.com/questions/12954193/why-does-division-by-zero-with-floating-point-or-double-precision-numbers-not

這個提問者提出的問題翻譯過來,和我前面遇到的問題一模一樣:

為什麼用 Float 或者 Double 除以零不會丟擲 java.lang.ArithmeticExceptionL:/by zero 異常?

這個問題下的高贊回答是這樣的:

問題的終極答案就藏在這個高贊回答中,我給你解析一番。

揭祕

這個高贊回答,其實就只有一句話:

In short, that's the way it's specified in the IEEE-754 standard, which is what Java's Floating-Point Operations are based on.

其他的部分都是參照。

在這一句話中,他提到了兩個關鍵的東西:

  • IEEE-754 standard
  • Java's Floating-Point Operations

意思就是 Java 的浮點運算是基於 IEEE-754 標準來的。

他給的其中一個超連結是 Java 語言規範:

https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3

Java 語言規範表示:你要問我為什麼,我只能告訴你我遵守的是 IEEE 754 這個國際規範。

所以,別問:

那麼這個 IEEE 754 是個什麼東西呢?

我也不知道,所以查一下:

好傢伙,來頭還不小。

IEEE,全稱 Institute of Electrical and Electronics Engineers,電氣和電子工程師協會。

IEEE 754 的全稱是 IEEE Standard for Floating-Point Arithmetic。 表示電氣和電子工程師協會制定的浮點運算技術標準。

Standard,標準,你明白吧?

得有一些 Standard,有些事情才好辦,不然各自為戰,各自相容,難受的一比。

所以,該標準是為了解決在不同的浮點實現中的各種問題,這些問題使它們難以可靠地使用和移植。

一旦有了標準,大家都遵守,就好辦了。

在標準中就規定了對於異常應該如何處理:

來,框起來的部分,跟我大聲的朗讀一遍:

Division by zero: an operation on finite operands gives an exact infinite result, e.g., 1/0 or log(0). By default, returns ±infinity.

針對「除以 0」異常,IEEE 754 規定:對有限運算元的運算會得到一個精確的無限結果,例如,1/0 或 log(0)。預設情況下,返回 ±infinity。

那麼問題又來了?

為什麼標準中要這樣的規定呢?

在前面提到的高贊回答中,給到了這樣的一個連結:

https://web.archive.org/web/20180112211305/http://grouper.ieee.org/groups/754/faq.html#exceptions

這個問題的答案就藏在這個連結裡面:

請問:為什麼除以零(或溢位,或下溢)不會停止程式或引發錯誤?

下面給了一大段回覆,我嘗試著理解了幾次,但是我發現有點超綱了,確實不知道具體啥意思。

我個人淺顯的認為它要表達的意思是:這玩意使用範圍很廣,為了程式的穩定性,我不想丟擲異常來終止程式,而使用者應該知道我這個「除 0 之後是一個無窮大的數」這樣的設定。

所以到底為什麼呢?

好了,

別問了,

就到這吧。

再問,

就不禮貌了。

看完之後,你只需要記住一句話:在 Java 裡面,除數作為 0,不一定會丟擲 ArithmeticException,千萬不要形成這樣的固化思維,從而影響自己排除問題的方向。