聊一聊Java中double精度去哪了(2)

继续先前的话题,double奇葩的精度。试完了C,为何不看看其他的语言,如python,毕竟基本上现在的编程语言基本采用IEEE754标准,存储方式相同,计算由CPU完成,结果为什么会不同?果然使用python得出的结论也是0.060000000000000005,不免怀疑之前C的结果。C的语句是这样的:

printf("%lf", 0.01 + 0.05);  #输出结果为0.060000。

码农的直觉告诉我,似乎有些东西少了。是精度,保留的精度不对,于是设定保留小数点后18位,因为java的输出小数点后有18位:

printf("%.18lf", 0.01 + 0.05);  #输出结果为0.060000000000000005。

这才是真相,线索越来越多,0.01+0.05这道题,可能跟语言无关,而是跟IEEE754标准有关?

这个实验当中,结果比预期的多,那么有没有比预期的少?

System.out.println(0.09 + 0.01);  #输出结果为0.09999999999999999。

但是,如果取小数点后10位以内,结果还是好的,而且一般现实也不需要那么高的精度,但是,倘若每次计算都做一次round,势必性能大打折扣!然而不做round,的确是不严谨的作法,如果是支付场景,0.09+0.01<0.1,那么这次交易就完成不了,这是绝对不能容忍的错误!

谈了现象,再谈谈原因,其实很简单,10进制的世界,对于0,1的世界的计算机来说,有点悬。

聊一聊Java中double精度去哪了(1)

前段时间,因为要测试一个刚出炉的高频策略,放实盘去跑吧,怕出岔,所以写了个简单的回测系统,跑一遍历史数据。其中有一部分是关于撮合系统,简陋了点,还算能跑得起来,几个用例下来,也没什么问题,接着增加历史数据量,居然出现了负数,简直不可能发生的事情居然出现了,虽然都是小金额的偏差,但是毕竟跟钱打交道,必须谨慎,况且现在比特币那么贵,丝毫偏差都是不允许的!

当然,后面就是苦逼的找bug,逻辑没问题,发狠的,把所有的数据都打印出来,日志一页一页没有尽头,心里发麻,硬着头皮一条条排查,人品不错,开头就发现一条异常数据,0.05+0.01=0.060000000000000005,瞬间明白,google it,才发现Java的double原来精度那么蛋疼。网上推荐BigDecimal代替double,果然不错,那就用BigDecimal替换。等所有的double都换之后,狗血的事情发生了,BigDecimal是如此的慢,以至于跑一个用例花了之前N倍的时间,怎么办,只能用一个折中的办法,数值表示仍然用double,数值计算用BigDecimal,于是乎,有了如下的一个四则运算工具类MathUtil.java

打破沙锅问到底--Java的@符号

刚接触Java变成,就经常碰到“@”这个符号,所谓的Java中的annotation。很多时候它都是跟@Override,@Deprecated,@SuppressWarnings,或者更见多识广一些,还有@Retention,@Target,@Documented,@Inherieted,但是,大多数情况下,很少会有人去过问为什么这么去写,只知道这是一种理所当然的写法,在这里推荐一篇非常详细的关于Java注解的博文Java注解annotation用法和自定义注解处理器

读完上面那篇文章,相信大家应该对Java的注解有了比较初步的认识,当然,光看别人的不行,没有自己的总结体会,等于没有学到太多。

原文中提到“自定义注解是以@interface为标志的”,那么,“注释”到底是不是一个interface呢?答案可以从官方文档中找到,“Annotation types are a form of interface”,如是写道。

抛硬币也能抛出人生大赢家

抛硬币,玩家庄家概率对半开,怎么赢,关键看下注策略。资金管理领域,有两种不得不提的策略,等价鞅与反等价鞅,从表现形式看,使用等价鞅制度的资金管理方式,在出现亏损以后,倾向于使用增加投入金额的方式进行后来的游戏。一旦获得盈利后,资金投入比例会再次回到起始水平;使用反等价鞅制度的资金管理方式,在出现亏损以后,会倾向于减少投入金额的比例进行后来的游戏;而伴随盈利的增加,也会不断增加投入资金,更多相关的内容见等价鞅和反等价鞅,写的很精彩,不做赘述。在这里,抛硬币属于古典概型,理解起来,相对简单直观。

游戏开始,你有10000块钱,资金翻倍后离场,不然一直到次数上限

等价鞅策略:

1:如果前一次赢了,本次下注金额为1元.  
2:如果前一次输了,本次下注金额为之前两倍,这样做到本次赢了,能把之前输的都回本.  
3:第一次玩时,认为前一次是赢的,因此,开局赌注为1元.

聊一聊不一样的比特币找零机制

刚开始接触比特币的时候,一定很多人困惑于比特币的找零,幸好资料很多,随手从网上找了一篇,很详尽《详解比特币的找零机制》,大家可以做一个入门导读。

好了,针对原文的一些概念,这里我做一些我的理解。

原文中“比特币钱包交易100次以上时再次交易后要重新备份钱包”,值得一提的是,这里的交易指的是支出,而不包含接收。

原文中举了一个买棒棒糖的例子,“新创建的货币金额与被销毁的货币金额是完全一样的”,这种说法其实不对,这里根本没有销毁和创建一说,无非是原地址比特币数字归零,余额赋值到新地址,正是因为原文中的说法,导致很多人以为比特币是完整不可分割。

原文中,“同时为了防止双重支付和伪造,必须确保在任何时候,新创建的货币金额与被销毁的货币金额是完全一样的。”,“销毁”和“创建”金额相同,跟双重支付和伪造够不成因果关系,确认交易和防止双重支付主要是有PROOF-OF-WORK机制保证的,就是常说的PoW。

脱离低级趣味的写作

之前的大多博文,东抄西抄,完全体现不出个人的劳动成果,作为一个研究僧,的确应该写一些自己独立思考的文字,挖掘深层次的知识。

其实现实当中可以书写的素材非常丰富,一篇博客,一纸论文,一本书,都能引出无数的话题,关键在于自己愿不愿挖掘,有没有探索的精神,这个非常重要,本科学习过程中,总能感觉自己遇到困难就撤退,很多事物浅尝辄止,泛泛而学,完全不应该是一个胸怀志向的年轻人的所作所为。

早些年读书,读不好,很大程度是不会读书,做任何时候,都有套路,就像游戏攻略一样,如果自己沉下心去思考,肯定会有领悟。谈到读书,不能泛泛的读,一本书,狼吞虎咽的浏览,翻到最后一夜,可能前面的内容都一点就没有了印象。除非天资聪颖,不然,认真翻个几遍真的是很有必要的事情。古人说,读书百遍,其义自现,当然,百遍是夸张的,但是没有多次的回顾,其实也是很难有所收获的,回顾的意义在于发现过去所遗漏的,或者未尝试思考的,次数多了,深度也就有了,体会也便油然而生,这样才能真正有自己的产出,而不是完全书上的注解。

Simple Git Guide

Preliminary

Remote

If you have not cloned an existing repository and want to connect your repository to a remote server, you need to add it with

git remote add repo_path

repo_path can be something like https://github.com/sunnycomes/okcoin_pc.

branch

Branches are used to develop features isolated from each other. The master branch is the "default" branch when you create a repository. Use other branches for development and merge them back to the master branch upon completion.

create a new branch named "branch_xx",

git branch branch_xx

and switch to it using git checkout branch_xx

or, git checkout -b branch_xx

do the same work.

Fork me on GitHub