周末的时候,我在网上看到一个关于微信钱包提现时,手续费收取的一个问题。
说真的,就这个问题吧,我个人觉得,放眼整个金融界,乃至于整个弱智吧,甚至于整个东半球,这都是一个相当炸裂的问题啊。
一时间,我居然恍惚了起来:一眼看去,漏洞百出。但是仔细分析之后,居然 TMD 无懈可击?!
哎呀,这个问题,你就不能细琢磨,一琢磨,脑瓜仁就疼。
你知道的,我是一个行动派,所以我肯定得先验证一波微信提现的手续费是否是这么多。
于是我发起提现了,发现确实是有至少一角钱的手续费:
另外,我发现安卓的手机,在这个页面中还无法截图,所以我就只有通过拍照的方式搞到这个图片了,所以看起来有点别扭,你多担待一下小老弟。
那么第一个问题就来了:我提现一角,它手续费一角。请问我最终到手是多少钱呢?
0.1(元)-0.1(元)=0(元),提现的钱全部扣了手续费,所以没有钱到手。
对吗?
逻辑上是合理的,但是如果微信真的敢这样做的话,不就显得很可(傻)爱(逼)吗?
给你举个例子:假设,我找你借了 100 元钱,然后我通过微信还给你的银行卡(假设微信支持这个功能,类似于跨行转账),此时这笔转账对应的手续费是 1 元。如果从转账的金额里面扣除,你收到的钱是 99 元。
你觉得这合理吗?
如果你觉得合理的话,那么请借给我 1w 吧,我应个急,十分钟后就还你 9900 元。
什么,你问我还有 100 元呢?
别问,问就是手续费,你找微信要去。
所以,正常的逻辑是从你的余额里面扣除。比如在我余额还有 141.02 元的时候,我提取了一毛钱,那么我的余额变成了 140.82 元:
这里,我还隐藏了一个逻辑。
比如你只提现一分钱的时候,如果你的微信余额大于 0.1 元,那么也是要收取 0.1 元的手续费的。
这句话,听起来就想要流泪。
既然是从余额中扣除,那么当我余额只有一毛钱的时候,我再次提现一毛钱,这个时候余额不够扣了,会出现什么情况呢?
我也赶紧试了试:
140.82(元)-140.72(元)=0.1(元)
所以,我先给 Max 同学转给了 140.72(元):
此时我的微信钱包只剩下了 0.1 元:
这个时候,我再次提现 0.1 元的时候,发现微信居然告诉我:本次提现免费!!!
由此可得,当微信里面剩下的钱不够扣手续费的时候,本次提现就会免费。
这个免费,圈起来,后面要考。
实验做完了,先把钱要回来再说:
啊!
大意了啊!
这样一来,我这篇文章的成本就很高了啊。我居然一时间被实验冲昏了头脑,主动上交了私房钱?
但是我还有一个实验场景没有做啊?
就是当我微信钱包里面的钱大于 0.1 元钱的时候,我点击“全部提现”会出现什么场景呢?
于是我以做实验的正当理由,成功的要回了一分钱:
这样,我的余额就变成了 0.11 元:
于是,当我点击“全部提现”的时候,虽然我已经预想到是这个场景了,但是我整个人还是沉默了,深深地沉默了。
我提现 0.11 元,手续费 0.1 元,到账 0.01 元。
也就是说,当你全部提现,且提现的金额大于手续费,即 0.1 元的时候,微信的逻辑是从你提取的钱里面扣除手续费。
也就是说我前面举得转账的例子,是真的有可能出现转出去,钱少了的情况。
麻绳专挑细处断,厄运专找苦命人啊!
现在,我已经得到结论了,所以我不能再输入密码了,再输入密码,又得痛失一毛钱!
实验现在已经结束了,结论我们也已经有了。
那么,接下来,我们再看看最开始的,那个让整个弱智吧,都为之“炸裂”的问题:
通过上面的实验,我们得知,这个问题中的这句话“如果我每次都只取 0.1,然后它手续费收 0.1”是没有任何问题的。
后半句:“就等于我一分钱都没有拿到”。
这句话是值得商榷的,因为通过实验证明,我最开始的时候,确实银行卡到账了 0.1 元。
但是,你要注意,我说“但是”了。
比如,我 1 元钱,每次提取 0.1 元,手续费 0.1 元,这样 5 次之后我微信里面的 1 元钱就变成了银行卡 0.5 元和微信收取的手续费 0.5 元。
那么,如果...
我是说如果,我把我银行卡里面的 0.5 元再次充回到微信里面,继续重复上面的动作,事情是不是就开始变得有趣了?
所以,面试编程题就来了,请听题:
已知,在微信钱包提现任意金额,都会收取至少 0.1 元的手续费,但是当余额不足 0.1 元时除外。假设,小明现在有 100 元,他应该怎么操作,才能把这 100 元钱,尽可能的全部变成手续费,白白送给微信?
请给我一段 Java 代码,入参是微信钱包里面的余额,日志打印出对应的操作过程。
拿到题,先不慌,分析一波。
首先,100 元,如果我每次只提取 0.1 元,收取 0.1 元手续费,那么当我操作 500 次之后,我还有 50 元。500 次,刚好是微信余额,100 元乘以 10,单位转化为角之后,再除以 2。
再把 50 元,存回去分 250 次取出来。250 次,刚好是微信余额,50 元乘以 10,单位转化为角之后,再除以 2。
再把 25 元存回去分 125 次 取出来。125 次,刚好是微信余额,25 元乘以 10,单位转化为角之后,再除以 2。
再把 12.5 块存回去分 62 次取出来,...
再把 6.2 存回去分 31 次取出来,...
循环往复,对吧。
也就说我每操作一次,我的微信余额会少 0.2 元。
结合前面举得例子,不难推理出来,我每一轮的操作次数,等于微信余额乘以 10,单位转化为角之后,再除以 2。
这个程序不难吧,起手就来:
public static void sbBehavior(double amount) {
//应该还有 amount 小于 0 的边界条件,节约篇幅,不写了。
if (amount <= 0.1) {
//微信里的钱不够了扣手续费了,操作结束
System.out.println("麻花藤:你只剩下:" + amount + "元了,谢谢老铁~");
return;
}
//金额扩大十倍,元转角,好计算
double totalJiao = amount * 10;
//每一轮的操作次数,等于微信余额除以 2
int count = (int) (totalJiao / 2);
//每一轮结束之后,共计手续费
double fee = count * 0.1;
//每一轮结束之后,银行卡里剩下的钱
double remainder = count * 0.1;
System.out.println("微信钱包原金额 = " + amount + "元,操作次数=" + count + "次,手续费=" + fee + "元,剩余金额=" + remainder + "元");
//把银行卡里剩下的钱充回微信,开始下一轮
sbBehavior(remainder);
}
好,按照前面的思路,我写出了这个程序,你就先看这个程序有什么问题。我就明着告诉你,这个程序肯定是有问题的,你就去琢磨,到底有哪些问题。
来,我问你:谁教你金额计算用浮点型的?回去等通知吧。
当我们的入参为 100 的时候,上面那个程序跑完之后,你会发现结果是这样的:
所以,牢记在心,只要涉及到金额的计算,一定一定一定要用 BigDecimal。而且我还附送你一条职场保命心经:用到 BigDecimal 时,具体保留多少小数位,具体的四舍五入规则,一定一定一定要让需求提出方白纸黑字的写在需求里面,而不是你自己想当然的认为,保留两位小数,采用四舍五入就行。后面出问题了,你啪的一下,就是把需求拿出来,你就不会很被动了。
回到我们的程序中,所以我们应该把程序修改成这样:
public static void sbBehavior(BigDecimal amount) {
if (amount.compareTo(new BigDecimal(0.1)) <= 0) {
//微信里的钱不够了扣手续费了,操作结束
System.out.println("麻花藤:你只剩下:" + amount + "元了,谢谢老铁~");
return;
}
//金额扩大十倍,元转角,好计算
BigDecimal jiao = amount.multiply(BigDecimal.TEN);
//每一轮的操作次数,等于微信余额除以 2
BigDecimal count = jiao.divide(new BigDecimal(2), 0, RoundingMode.DOWN);
//每一轮结束之后,共计手续费
BigDecimal fee = count.multiply(new BigDecimal(0.1));
//每一轮结束之后,银行卡里剩下的钱
BigDecimal remainder = count.multiply(new BigDecimal(0.1));
System.out.println("微信钱包原金额 = " + amount + "元,操作次数=" + count + "次,手续费=" + fee + "元,剩余金额=" + remainder + "元");
//把银行卡里剩下的钱充回微信,开始下一轮
sbBehavior(remainder);
}
在上面的程序中,我把参与运行的地方全部都改成了 BigDecimal。但是这个程序还是有问题。
来,你继续去琢磨,到底有哪些问题?
来,我问你:谁教你用 BigDecimal 参与计算的时候,用 new BigDecimal(0.1) 这个构造方法?
你用这个方法,idea 都会提醒你:老铁,听哥哥一句劝,还是用 String 类型的构造函数稳妥一点。
就上面这个程序,我给你跑一下,你就发现问题了,同样还是有浮点数的问题:
所以,程序还得改一下,改成用 BigDecimal 的 String 类型的构造函数,其他啥都不动:
好,这个问题算是解决了。
你继续说,这个程序还有啥问题?
如果你没看出来的话,那么我带你看看输出结果:
在这一次输出的时候,手续费 6.2 元,剩余金额 6.2 元,加起来才 12.4 元。但是我“微信钱包原金额”是 12.5 元啊?
还有一分钱去哪里了呢?
所以我在一开始分析题的时候就给你下了一个套:
100 元,操作 500 次之后,还有 50 元。50 元,操作 250 次之后,还有 25 元。25 元,操作 150 次之后,还有 12.5 元...
如果你没有带着自己的思考看文章的话,那么你可能就默认为操作一次之后,手续费和银行卡的余额都会增加 0.1 元。
也就是手续费和银行卡的金额,和操作次数相关,所以写出了这样的代码:
手续费,确实是每操作一次之后扣除 0.1 元,确实是和操作次数正相关。但是剩余的钱,应该是用当前这一轮剩余的总金额减去当前这一轮的总手续费。
也就是要把这一行代码修改为这样:
拿着这个程序去跑的时候,你会发现输出正常了,每一轮的金额加起来都能相等了:
这下没有任何毛病了。
那么,注意,我现在要开始变形了。我要把题目变成:
请给我一段 Java 代码,入参是微信钱包里面的余额,出参是一共需要操作多少次。
在题目中,加了总操作次数的出参,我已经知道了每一轮操作的次数,算总次数这还不是手到擒来的事情?
分分钟拿出代码:
跑出结果:
我们可以看到是 999 次。是的,不要质疑这个结果,当你有 100 元钱的时候,只需要操作 999 次,你就把自己的 99.9 元都给到微信了。
诶,朋友,你注意看,当我把金额变成 50 元的时候,总次数就是 499 了:
当我把金额变成 9.9 元的时候,总次数就变成了 98 次:
所以,请注意,我要“所以”了。
所以,如果我只要求操作的总次数,不要求输出过程,那么代码应该是怎么样的?
是不是把金额扩大十倍,变成角票,然后减去自己留下的一角钱,就是操作的总次数:
public static int sbBehavior(BigDecimal amount) {
return amount.multiply(BigDecimal.TEN).subtract(BigDecimal.ONE).intValue();
}
这样不就完事了吗?
你忘记前面的所有内容,仔细的想想,是不是确实是这个道理?
假设你有 100 元,无论你怎么操作,微信每次只会收 0.1 元的手续费,而你最多只会剩下 0.1 元。
那么你肯定得至少操作 999 次啊,这个小弯儿能转过来吧?
好,转过来了,是吧?
我再问你一个问题,假设我只有 0.19 元,我要把钱给微信,最多操作一次,然后我给它 0.1 元对吧?
但是,你用上面我给你的代码跑出来只会,输出是 0:
是的,这个代码还是有问题的。
我就明确的告诉你,这个代码只适用于金额在 100.9 元到 0.19 元之间的数字。
至于为什么,自己去琢磨。但是我不建议你去琢磨,因为这个玩意整个从最开始的地方就走偏了。
现在,请你忘记前面所有的代码,因为前面的代码,全都是错的,我全程都在误导你,让你顺着我的思路走。
其实你回想一下,最开始的时候,我为什么要假设你微信里面只有 100 元钱?
因为 100 元钱对应的手续费,不论你是提取一角钱,还是提取 100 元,100*0.001=0.1元,都刚好是 0.1 元。
然后我就开始告诉你,每次提现到银行卡 0.1 元,手续费 0.1 元,巴拉巴拉巴拉~
但是,你有没有想过,或者是看到哪个部分的时候,才恍然大悟:如果我有 1000 元呢?
如果我有 1000 元,那么我第一次全部提现的话,手续费就是 1 元啊,而不是 0.1 元啊?
所以,你现在回过头去看这行代码,是不是特别的搞笑:
怎么会去先计算次数,再根据次数反算其金额呢?
为了尽快的把钱都给到微信,肯定是每次尽量给到更多的手续费。已知手续费率是固定的,那么提现的金额越高,手续费越高对吧?
所以,正确的操作应该是每次把微信钱包里面的钱全部都取出来,也就是基于微信钱包里面的钱,去计算手续费,计算剩余的钱。
转变了核心思路之后,代码就变成了这样:
我也给你放一个粘过去就能用的代码:
public static int sbBehavior(BigDecimal amount, int totalTimes) {
if (amount.compareTo(new BigDecimal("0.1")) <= 0) {
//微信里的钱不够了扣手续费了,操作结束
System.out.println("麻花藤:你只剩下:" + amount + "元了,谢谢老铁~");
return totalTimes;
}
//基于微信钱包里面的钱,去计算手续费
BigDecimal fee = amount.multiply(new BigDecimal("0.001")).setScale(2, BigDecimal.ROUND_UP);
//手续费不足 0.1 元,则补齐为 0.1 元
if (fee.compareTo(new BigDecimal("0.1")) <= 0) {
fee = new BigDecimal("0.1");
}
//提现到银行卡的钱
BigDecimal remainder = amount.subtract(fee);
totalTimes++;
System.out.println("原现金 = " + amount + "元,操作=" + totalTimes + "次后,手续费=" + fee + "元,还剩下=" + remainder + "元");
//把银行卡里剩下的钱充回微信,开始下一轮
return sbBehavior(remainder, totalTimes);
}
这样,当我们有 1000 元的时候,每次做“全部提现”的动作,只需要操作 3257 次:
如果我们还是用之前一毛钱一毛钱的提法,得搞 9999 次。
效率提升 300%+。
舒服了!
而且这个是通用的逻辑,你就算给它 100 元,它也能给你算出是 999 次:
给它 0.19 元,它能给你算出是 1 次:
没有任何毛病,但是,不知道你看到这里,是否产生了一个疑问:为什么我们要把钱尽可能的给微信呢?
那我给你换个角度:我们应该怎么操作,才应该避免给微信手续费呢?
这样一想,是不是思路就打开了?
最后,如果这篇文章有那么一个瞬间,让你笑了一下的话,那么,求个免费的“赞”,不过分吧?如果可以的话,关注一下我的公众号“why技术”那就更好了,文章在公众号全网首发