開發者的損益兩平價格,是總收入恰好等於總成本時的單位價格,計算方式是把(固定成本加上單位成本乘以銷售數量)除以扣除平台抽成與金流手續費之後的銷售數量。對軟體、遊戲與 App 開發者來說,這代表在任何利潤開始出現之前,必須先讓開發支出、主機代管、商店抽成、行銷、退款與稅金這幾項達到平衡。由於許多計價引擎與金融協定,會把這些費率編碼成定點整數、打包過的位元欄位,或是有號與無號的 32 位元數值,底層這套位元運算邏輯,就必須在上線之前經得起檢驗。Programmer Calculator 會依照 ECMAScript 的 Number 位元運算語意,套用 AND、OR、XOR、NOT,以及三種位移運算子,然後把同一組 32 位元的模式,同時呈現為有號十進位、0 到 4,294,967,295 之間的無號十進位、八位數的十六進位,以及一串 32 位元的二進位字串。透過這四種檢視方式來檢查同一組位元,能讓開發者確認一個打包過的費率欄位、一個溢位防護機制,或一個等級遮罩,行為完全符合規格上所寫的內容,而不是仰賴一個會悄悄產生誤差的浮點數捷徑。

how to calculate developer breakeven price
how to calculate developer breakeven price

開發者損益兩平價格實際上代表什麼

損益兩平價格,是開發者要收回花在某條產品線、某次行銷活動,或某一次發行上的每一分錢,所需要的最低價格。高於這個價格賣出的部分是利潤;低於這個價格則是虧損。對單一 SKU 來說,可用的公式是:

損益兩平價格 = 總成本 ÷ (銷售數量 × (1 − 平台抽成 − 金流手續費 − 稅率))

銷售數量這一項之所以重要,是因為損益兩平只針對某個預估銷量才有意義。如果開發者預期賣出 5,000 份,損益兩平價格就是以 5,000 為基準計算出來的;如果是 50,000 份,單位損益兩平價格就會下降十倍。抽成這一項之所以重要,是因為像 Steam 這類商店,會在收入到達開發者手上之前先抽走 30%,而金流服務商還會再抽走 2–4%。忘記把這個除數算進去的開發者,最後訂出來的價格,會讓每一筆銷售都虧錢。同樣的邏輯,也適用於訂閱制產品、App 內購,以及靠廣告支撐的 App,只不過在這些情況下,除數變成的是曝光次數或月活躍使用者數,而不是銷售數量。

開發者必須加總起來的成本項目

總成本很少只是單一一個數字。它是好幾個項目加總起來的結果,而常見的錯誤,就是漏掉其中一項:

  • 開發人力 — 薪資、外包費用,或投入時間所產生的機會成本。
  • 工具與中介軟體 — 引擎授權、美術素材包、第三方 SDK、分析平台。
  • 主機代管與基礎設施 — 伺服器、CDN、資料庫、可觀測性工具,以及日誌記錄。
  • 行銷與使用者取得 — 廣告、網紅合作費用、商店頁面製作、ASO(應用程式商店最佳化)。
  • 商店抽成與稅金 — 以撰寫本文當下來說,Steam 抽成為 30%,視情況而定的加值稅或營業稅,以及金流手續費。
  • 準備金與退款緩衝 — 為了因應拒付與退貨,事先預留的一定比例金額。
一個簡短的實作範例,能讓答案的樣貌變得清楚。假設總成本是 $200,000,預期銷售數量是 10,000 份,商店抽成是 30%,金流手續費是 3%。那麼淨收入係數就是 0.70 × 0.97 = 0.679,於是:

損益兩平價格 = $200,000 ÷ (10,000 × 0.679) = $200,000 ÷ 6,790 ≈ $29.46

每份售價低於 $29.46 就會虧錢;高於這個價格,則會產生利潤,並隨著銷售數量按比例增加。這個算式本身很小,但除數右側每漏掉一項費用,就是開發者要自掏腰包吸收的一項費用。

為什麼位元運算會出現在損益兩平的計算裡

上面這套算式是十進位的,但計價引擎底層的儲存方式,通常並不是。抽成、等級旗標、貨幣指數與折扣遮罩,通常會被儲存成打包過的 32 位元整數,以便在伺服器、用戶端,以及重播交易之間,維持確定性的運算結果。有三種模式,在真實的計價系統中特別常見:

    定點費率。
  • 一個 30% 的平台抽成,被編碼成帶有九位小數的整數 300,000,000,而不是浮點數 0.30。
  • 等級遮罩。
  • 單一一個整數,其中每一個位元,分別表示一位顧客是否符合等級 A、B、C 或 D 的資格。
  • 有號溢位防護。
  • 用位移運算來偵測,累計總額是否超出了 −2,147,483,648 到 2,147,483,647 這個有號 32 位元範圍。
  • 由於這些數值是 32 位元的二補數表示法,同一組位元模式,就會有兩種合法的讀法——有號與無號。
Programmer Calculator

會把這兩種讀法並排顯示出來,這是確認一個打包過的費率欄位是否打包正確最乾淨俐落的方法。如果一名開發者在沒有做這項檢查的情況下,就把計價邏輯上線,就可能在累計總額第一次跨過正負號邊界時,讓程式碼算出錯誤的價格。這裡用到的每一個運算子的正式規則,都定義在ECMAScript binary bitwise operators specification 裡,並在MDN bitwise operators reference 中重新整理呈現。 用 Programmer Calculator 執行一次損益兩平檢查

這個計算工具的設計目的,是用在驗證這一步,而不是試算表那一步。在一個候選的計價數值已經被打包成 32 位元整數之後,如果你想在把這個值寫進標頭、合約,或資料庫欄位之前,先確認它的位元模式,就可以使用這個工具。

選擇你想要組合的運算元所使用的進位制。如果費率欄位是以十六進位寫成的,選 16 進位;如果它是從封包擷取記錄中以二進位寫下的,選 2 進位;其他情況則選 10 進位。

    為 AND、OR 或 XOR 輸入運算元 A 與運算元 B;為左移、有號右移,或無號右移,輸入運算元 A 與一個 0–31 之間的位移量;NOT 則只需要輸入運算元 A。剖析規則很嚴格:任何超出所選進位制範圍的數字都會被拒絕,因此十進位的 2,不會被悄悄當成二進位數字放行。
  1. 選擇運算方式——AND、OR、XOR、NOT、SHL、SHR,或 USHR——然後執行計算。
  2. 檢視四種結果檢視方式:有號十進位、無號十進位、八位數十六進位,以及 32 位元二進位。這四種檢視方式描述的都是同一組 32 個位元,差別只在解讀方式不同。
  3. 一旦這四種檢視方式都符合預期的規格數值,就可以複製格式化後的結果。
  4. 輸入值必須維持在單一一個 32 位元字組的範圍之內:有號下限是 −2,147,483,648,無號上限是 4,294,967,295。位移量刻意限制在 0–31 之間,對應這個字組實際的位元位置,不會隱藏 JavaScript 對 32 取模的行為。這四行結果,全部都是在本機從同一組位元計算出來的,因此整個運算過程都不會離開瀏覽器。
  5. 比較同一個結果的有號讀法與無號讀法

許多損益兩平相關的錯誤,都來自於用兩種不同方式讀取同一個數值。一個 4,200,000,000 的累計總額,當成無號 32 位元數值來看,看起來很正常,但如果用有號的方式來讀取同一組位元模式,因為最高位元被設為一,它就會顯示成一個很大的負數。這正是這個計算工具設計出來要揭露的陷阱。以 10 進位輸入 5,執行 NOT,計算工具會回傳有號 −6、無號 4,294,967,290、十六進位 FFFFFFFA,以及一串以三十個一開頭的二進位模式。十六進位檢視與二進位檢視,符合的是無號讀法;有號檢視則會告訴你,如果某種語言把這個欄位視為有號,JVM 或 C# 的 int 型別會看到什麼結果。在把這個值寫進設定檔或網路封包之前,請務必兩種讀法都確認過。

在鎖定計價邏輯之前,先確認位移行為

位移行為,正是計價引擎最常讓作者感到意外的地方。這個工具上的三種位移運算子,遵循的是 ECMAScript 的 Number 規則:左移會把位元往左移動,並在右側補入零;有號右移會把符號位元複製到新的高位位置;無號右移則會補入零,因此回傳的結果,會落在 0 到 4,294,967,295 之間。以最大的正有號 32 位元整數 2,147,483,647 為例。在計算工具中把它向左移一位,結果會是有號 −2、無號 4,294,967,294、十六進位 FFFFFFFE,以及一串以一開頭的二進位模式。之所以會產生溢位環繞,是因為超出 32 位元字組範圍的那些位元被捨棄了——這正是當累計總額有可能超過 2,147,483,647 時,開發者必須事先預料到的行為。請在上線之前,就把這種行為鎖進規格裡,而不是等到上線之後才處理。

在任何一個計價欄位(無論有號或無號)上線之前,套用下面這份檢查清單:

欄位第一次確認費率儲存為定點整數,而不是浮點數與候選位元執行 AND,會得到預期的子集合維持在有號 32 位元的範圍之內單一位元,NOT 能乾淨地將其反轉
再次確認
十進位換算結果,與規格數值相符 等級遮罩
結果中,不該有的位元都是零 累計總額
左移造成的溢位,已經由程式碼處理 退款旗標
無號讀法與有號讀法一致

這個計算工具不會顯示位元組順序,因為一個數值字組在被寫成位元組之前,並沒有所謂的序列化順序。對於一個二進位檔案或網路封包,請另外分別確認它是大端序還是小端序。這個計算工具回傳的補齊後位元模式,本身無法回答這個表示法上的問題;請把位元組順序,當成獨立於上述位元運算檢查之外的另一個驗證步驟。