為了弄清起點小說如何算字扣錢,我特意註冊了作家賬號

是奉壹呀發表於2023-05-04

閒來無事,想起這些年也算給起點貢獻了不少流量和金錢。
在起點的會員訂閱規則裡,如下圖所示,重點關注2,3和6點,一個是怎麼算錢的,一個是怎麼算字數的。
計費跟字數相關,歸根結底,都是怎麼算錢的事兒。

1. 怎麼算字數

“作品字數以起點計數系統為準。”

起點沒有公佈計數系統的統計標準。所以就有了本文,我們來猜一猜嘛。

在書架上隨手找了一本之前訂閱過的書,找了章公眾章節。
如下所示,共858字。

找個線上的OCR把圖片識別成文字,再手動檢查一遍,確保沒有錯漏。
然後使用java String的length()方法看看。
為了方便感興趣的同學,我把識別出來的文字也發出來。

String text = "截止到今天,日兩萬已經持續了整整十天,換句話說...這個月我已經更新了20萬字了。" +
                "原定計劃是日兩萬堅持到月底的,但最近大家的反饋讓我意識到,我不能太執著於數量了,文的質量也是櫃當重要的一環。" +
                "再加上...我寫的真的太快了,這個成績該有的推薦還沒到,我這本書的字數都已經快要攔不住了。" +
                "這個月底上導讀,而下個月說不定還有更好的推薦...考慮到整本書的壽命,我的確是該把更新的速度放緩。" +
                "不過大家放心,速度放緩並不代表會虧欠大家。" +
                "日更兩萬改成接下來兩個月每天日萬,總數還是不變的,只是時間會拉長一點.." +
                "原諒一下我的私心哈,我也想讓自己的作品能儘可能的壽命長一些。" +
                "好在我這本不是特別單純的單女主狗糧文,女主的身份可以讓這本書加一些文娛要素。" +
                "當然,就算是文娛要素,整本書的核心賣點肯定還是甜和日常。" +
                "所以就算後面會開一些事業線,事業線的作用更多的還是輔助日常與甜,不會鳩佔鵲巢,讓這本書的整體節奏變得奇怪。" +
                "而接下來還有很多可以寫的地方,像是鋪墊了很久的,這條讓小劉事業騰飛的事業線。" +
                "以及一些疑似敗犬的落寞退場(慘!)" +
                "等小裴回歸之後,準備搬上日程的公開。" +
                "還有公開之後各方反應,後續雙方漸漸交織在一起的事業線這些。" +
                "乃至於後面的嗑cp" +
                "當然,小劉的直播事業也不會落下,遊戲這方面可是作者的強項(笑)" +
                "————"+
                "減少更新其實讓我心裡挺不安的,畢竟我一直不敢相信是自己這本書寫得好,只是一廂情願的以為成績好是因為更新量大。" +
                "而現在更新量放了下來,我的壓力也算是拉滿了..." +
                "所以如果可以的話,我還是想腆著臉的跟大家要一個追訂。" +
                "資料對我真的蠻重要的,有的時候大家評論也會讓我開心很久。" +
                "嗯...不好的評論當然也會讓我emo很久,不過還好,我心理調節能力很強。" +
                "畢竟叫撲了600萬字了...我現在只想把這本書寫好,機會到面前了,我不想放棄。" +
                "從明天開始,每天雷打不動一萬的更新,將會持續很長一段時間。" +
                "但我應該會用跟現在一樣的時間去打磨每一章,儘可能的不讓大家感覺到無聊無趣。" +
                "望大家諒解!" +
                "啊,當然,會時不時的不定期進行爆更,說不定十更這種事...隔上個一兩週就來一次呢~" +
                "畢竟我們的手速大夥心裡都有數是吧〜";

        System.out.println(text.length());

輸出875。
很明顯多了。

幾個猜測,首先中文的任意字,字元肯定是算1個字元長度的,比如,, ,甚至中文多個句號當省略號。。。。。。也會被計算為6個字。
然後英文單詞只會計算成一個長度。
其次英文輸入法中的...也只會計算一個長度。

暴力點,直接先來一波驗證一下。

以下測試程式碼將英文輸入法下的...替換成一個字元,然後將連續的數字和英文單詞(包含創造出的網路詞彙英文縮寫,或者叫連線的英文字母,拼音)也當成一個字元長度。
具體做法是先替換為空,再在後邊統計的時候補上個數。


String temp = text.trim().replace("...", ".").replace("..", ".");
String s = "\\d+.\\d+|\\w+";
Pattern pattern=Pattern.compile(s);
Matcher ma=pattern.matcher(temp);

List<String> words = new ArrayList<>();
while(ma.find()){
    String word = ma.group();
    words.add(word);
    temp = temp.replace(word, "");
}

System.out.println(temp.length() + words.size());

輸出856。
但是起點計數系統統計出來是858。
差了2個字。


這章的標題是4個字,如果是標題的原因,那應該是差4個字,所以標題應該是不計數的。


也有可能是圖片轉文字的差異,可能是一些符號()不太容易分辨是哪種輸入法,還有看不出來是分隔符還是破折號的"———"造成的差異。


更直接點,我下載了一個起點的作家助手,將上述文字直接輸到公眾章節,讓起點計數系統統計,結果與我原生程式碼統計是一樣的。

從側面證明上述的猜測是對的。

所以起點真就是這樣直接使用String.length()計算出來的?

為了更加的嚴謹,我再新增了一部小說,《重生之不做程式設計師》。

第一章《英雄遲暮》,正文內容為“SUN公司被Oracle收購,是否意味著java被逼上了死路?”

 String text2 = "sum公司被Oracle收購,是否意味著java被逼上了死路?";
        String temp = text2.trim().replace("...", ".").replace("..", ".");
        String s = "\\d+.\\d+|\\w+";
        Pattern pattern=Pattern.compile(s);
        Matcher ma=pattern.matcher(temp);

        List<String> words = new ArrayList<>();
        while(ma.find()){
            String word = ma.group();
            words.add(word);
            temp = temp.replace(word, "");
        }
        System.out.println(temp.length() + words.size());

輸出21.

數字600000000被算作一個字。

特殊字元呢?
比如 ?
它被算作了兩個字元。

為什麼呢?
直接複製過去,可以看到它是兩個UTF 16編碼。所以長度為2。
String.length()其實統計的是這個編碼單元數。

如果有特殊字元編碼的文字,需要精確統計字元數,可以使用codePointCount方法。
在原來程式碼基礎上新增以下測試程式碼。

String b = "?";
System.out.println(temp.length() + b.length() + words.size());
System.out.println(temp.length() + b.codePointCount(0, b.length()) + words.size());

分別輸出

858
857

這部份可以參考以下這篇文章 ,《美團面試官問我一個字元的String.length()是多少,我說是1,面試官說你回去好好學一下吧》

https://juejin.cn/post/6844904036873814023

總之,它都能從側面證明,起點小說統計字數大機率就是使用的length(),同時將連續數字,英文單詞算作單個字元,同時英文輸入法下的省略號等符號算作一個。同時中文輸入法下的任意每個字元都計數。
特殊字元長度需要看它的字串中的 Unicode 程式碼單元的數目。

2. 怎麼算錢

眾所周知,在java裡,涉及到錢的計算,必須使用BigDecimal,才能精確計算。

如果使用double,float會丟失精度。

假設一個VIP章節共有字數4689,普通會員每千字5分錢,那麼訂閱該章節需要多少錢?

使用double/float用兩種方法進行計算

int wordCount = 4689;
int monovalent = 5;
float money = (wordCount * monovalent) / 1000;
System.out.println(money);

double money2 = (wordCount / 1000) * monovalent;
System.out.println(money2);

分別輸出

23.0
20.0

先乘後除只是丟失精度。
先除後乘不僅僅丟失精度,丟失的精度經過乘法放大,其結果就相差越大了。

透過強轉能得到正確結果,這裡只是演示。

float money = (float) (wordCount * monovalent) / 1000;
double money2 = ((double)wordCount / 1000) * monovalent;

使用BigDecimal進行計算

System.out.println(
       BigDecimal.valueOf(wordCount)
       .divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP)
       .multiply(BigDecimal.valueOf(monovalent)).doubleValue());


System.out.println(
       BigDecimal.valueOf(wordCount)
      .multiply(BigDecimal.valueOf(monovalent))
      .divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP).doubleValue());

輸出

23.45  
23.45

這裡使用bigdecimal有兩點需要注意:

  1. 如果是數字,double,float在構建bigdecimal物件時,不要直接使用new BigDecimal(double/float),這裡會出現精度丟失,它可能會無限接近準確值,不能完全精確。
double number = 2.352356234234236D;
System.out.println(number);
System.out.println(new BigDecimal(number));  // 輸出近似值
System.out.println(BigDecimal.valueOf(number));
System.out.println(new BigDecimal(String.valueOf(number)));

輸出

2.352356234234236
2.352356234234235898838960565626621246337890625
2.352356234234236
2.352356234234236
  1. 進行divide除法操作時,需設定RoundingMode,避免除不盡出現無限迴圈小數時,丟擲異常
    Non-terminating decimal expansion; no exact representable decimal result.

不管是先乘後除還是先除後乘,都能得到正確的結果。

同時上述的計算結果單位都是:
23.445分,四捨五入為23.45分。

我們知道日常交易金額的最低單位為分,如果再精確就出現了

對於釐的處理,起點的選擇是直接抹去小數部份。不管23.445還是23.45都直接處理為23分錢。

同時起點不支援每章節直接使用人民幣付費,而是先充值為起點幣,1起點幣=1分錢。
所以訂閱上述章節需支付23起點幣。

這種方式,餘額沒有小數點。儲存時可以直接使用int型別,充值扣費時也沒有小數點精度的問題。


2.1 起點為什麼要抹零,它是良心資本家嗎


隨便一搜,網上隨處可見的,起點霸道合同,壓榨作家寫手的新聞。
23.45抹零,23.99也抹零。

那麼,奇了怪了,起點為什麼會在付費金額這裡直接抹零了呢?蚊子再小也是肉啊!

對待金額超出的部份,有個經典的銀行家舍入法。
銀行的一大部份利潤來源是存貸的利息差。對於超出的部份金額怎樣取捨,直接關係到銀行的盈虧。
如果對這部份金額直接採取四捨五入,銀行可能是會虧錢的。

假設銀行有如下10筆利息

0.000、0.001、0.002、0.003、0.004
0.005、0.006、0.007、0.008、0.009 

如果採取傳統的四捨五入方法的話,
銀行的盈利分別為

0,+0.001,+0.002,+0.003,+0.004
-0.005,-0.004,-0.003,-0.002,-0.001

這樣相加,可以得知,銀行虧損了0.005。

為了應對這種情況,銀行家們開發了一種新的演算法。故名銀行家演算法。

  • 只保留兩位小數(角分)的情況下,看第3位小數(釐),如果第3位小數不為5,則直接四捨五入
    * * 如果第3位小數為5,如果第4位小數不為0,則統一進位
    * * * 如果第4位小數為0或者叫沒有第4位小數,則看第2位小數的奇偶
    * * * * 第2位小數為奇數捨去
    * * * * 第2位小數為偶數則進位

在java中,BigDecimal提供了一種ROUND_HALF_EVEN的舍入方式,即為銀行家舍入法。

以下是程式碼演示:

 /**
 * 只保留兩位數的情況下,看第3位小數,如果第3位小數不為5,則直接四捨五入
 *     如果第3位小數為5,如果第4位小數不為0,則統一進位
 *         如果第4位小數為0或者叫沒有第4位小數,則看第2位小數的奇偶
 *             第2位小數為奇數捨去
 *             第2位小數為偶數則進位
 */
System.out.println(new BigDecimal(String.valueOf("1.256")).setScale(2, BigDecimal.ROUND_HALF_EVEN)); // 1.26
System.out.println(new BigDecimal(String.valueOf("1.254")).setScale(2, BigDecimal.ROUND_HALF_EVEN)); // 1.25
System.out.println(new BigDecimal(String.valueOf("1.2551")).setScale(2, BigDecimal.ROUND_HALF_EVEN)); // 1.26
System.out.println(new BigDecimal(String.valueOf("1.245")).setScale(2, BigDecimal.ROUND_HALF_EVEN)); // 1.24
System.out.println(new BigDecimal(String.valueOf("1.255")).setScale(2, BigDecimal.ROUND_HALF_EVEN)); // 1.26

輸出

1.26
1.25
1.26
1.24
1.26

對於起點為什麼沒有使用銀行家演算法,而是對小數部份直接抹零,我只能理解是錢太好賺了,它提供了一個平臺,哪怕一隻鳥飛過平臺留下一根毛,起點也要分一半。這點零頭就不計較了。

但我自己都不信,我可能懂那麼一點技術,但我不懂資本家 。

同時,談到銀行家演算法處理利息,對於起點也有一個關於利息的疑問。


2.2 起點幣餘額沒有利息合法嗎?


先說個題外的,
支付寶的餘額寶有利息,微信零錢沒有利息,微信合法嗎?

知乎大佬給出的法律界定,
微信零錢不會轉移走客戶的資金,比如拿去放貸,所以不會給利息,這是合法的,給了利息反而不合法。

但這能證明起點不給利息合法嗎?

微信零錢作為支付的中介,微信本身不提供商品。
而起點呢?起點本身提供文字類商品,且不支援直接支付,必須要充值賬戶才能消費。

對吧,起點不支援點選訂閱按鈕,發起銀聯或者支付寶微信支付等3方支付,必須先充值起點幣。
起點賬戶裡幣本身就是人民幣,不是說叫充值幣就能談化錢的概念了。
起點有把這部份餘額拿出去做其它放貸或其它用途呢,《使用者協議》裡面沒有表述。

你能想象嗎?
淘寶或者京東,購物時不支援三方支付,必須要先購買淘寶幣或者京東幣,然後再用幣支付。
同時,剩下的幣不支援提現,也沒利息?
如果你不同意這種支付方式,就不能購物。

假如我充值了一百塊錢,訂閱了某個章節使用了幾毛錢,然後索然無味,沒再繼續訂閱後續章節,那賬戶裡剩下的錢一直凍結在專用賬戶裡沒有用作它途產生非法的高額利潤?
就算沒有,這筆錢躺在銀行賬戶裡本身也是有利息的,這部份利息肯定是沒有向我本人發放的,這合法嗎?


我想這才是起點在訂閱時直接對小數點抹零的動機吧。
背後的原因令人三級燙傷。


至於其它的,安卓端充值不能在蘋果端消費(最早WEB端也不能),



訂閱不是永久性的,已訂閱商品可能需重複購買這些感覺都是小事了(給人的感覺就是,起點拿訂閱打賞的一半的時候毫不手軟,出問題的時候獨善其身趕緊撇清)。



參考:
https://developer.aliyun.com/article/684424
https://juejin.cn/post/6844904036873814023

相關文章