为了弄清起点小说如何算字扣钱,我特意注册了作家账号
闲来无事,想起这些年也算给起点贡献了不少流量和金钱。
在起点的会员订阅规则里,如下图所示,重点关注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有两点需要注意:
- 如果是数字,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
- 进行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