圆周率绘图

自从翻译了维基上那篇BBP的文章一直想做这个用圆周率绘图的小程序。今天终于尝试了一下。可惜由于php的性能问题,没法绘出我想要的那种整幅的具有震撼效果的圆周率的外貌图。。。
目前只好用一个小小的24*24的小图先凑合着。其实BBP是个并行算法,要并行起来跑才会快的。但是php的多进程执行实在有点麻烦。所以准备以后有机会再来进一步做下去了。

图片正在加载,请等待

集合论研学初心

最近在网上看了几位大牛的博客,对集合论逐渐产生兴趣。进而去维基上翻了翻,对集合论有了个初步的了解。这里做点笔记。

集合论是一个目标统一全宇宙的超级理论。当然也有可能他统一不了了,但透过集合论,去理解人类逻辑的固有定式,去理解数学如何解释世界,是很有意思的事情。

朴素集合论是很容易理解的事情。首先有元素这个概念。然后元素可以属于集合。两个集合相等完全由其元素一一相等来判定(外延公理)。然后集合的元素也可以是集合,集合可以为空(空集公理)。
这就是高中课本上的集合定义了吧。但这样的定义存在巨大的问题。

例如著名的罗素悖论:设存在集合R, 它包含的元素由对“所有集合”应用的一个函数(谓词)来确定,即“不包含自身的集合”(固有前提,该元素是一个集合)。这个谓词是合理的,因为在”所有集合”这个范围内确实存在不包含自身的集合。那么,将所有这样的集合作为元素,放到这个集合R里,理论上似乎可行。那么我们看这样的集合R有什么特性呢? 他或许不包含自身(R不属于R),如果是这样,那么他就应该包含R(因为R是所有不包含自己的集合嘛)。但是如果他包含了自己,他立刻就不应该包含自己了(包含自己的集合不应该属于R)。

为了阻止这种情况发生,严谨的集合论不得不增加一些公理和概念来回避这个问题。首先,使用一个谓词确定的集合不再确保构成一个集合。他们只是属于一类(内涵公理替代公理)。因此“”这个概念比集合更广泛。如果一个类不是一个集合(例如“不包含自身的所有集合”),那么他就是一个“真类”。
另外,在一个集合上使用一个谓词仍然可以得到所有符合这个谓词的元素的集合。这称之为分类公理
之后引入“正则性公理”来确保集合的合理性。这个公理描述起来有点费力。首先,公理化集合论一般认为所有集合都是集合的集合,不存在集合之外的概念。集合的元素也是集合(有可能是空集,所以这个定义是可以终止的)。然后正则性公理是说,任意非空集合,总存在一个元素,这个元素(同时也是集合)与这个集合的交集为空。这样理解很困难,我们反过来理解。任意非空集合,不可能发生这种情况:他的所有元素都与其相交。这仍然很难想象,我们举个例子来观察这样的集合的特性。
假设R符合这个条件,假设他包含一个元素。则 R = {A}
这里A和R相交非空,则A交R = B
B是A和R的交集,则B是R的子集。同时B非空。则B只能是{A}。也就是说,B = R。
所以A交R = B。A = {A, …}
而R = {A}, 所以R = {{A, …}} = {{{A, …}, …}} = {{{{A, …}, …}, …}}
这样的A是无穷递归定义的。或者说,在有限时间内无法写出R的定义。这和无穷集合并不相同。无穷集合只是在有限时间内无法写出这个集合中的元素而已。如果一个集合无法被定义,我们应该认为他就是不存在的吧。
R包含更多元素的情况比较复杂,不过归结到最后都是如这样形式的无穷递归。可以说这个公理防止了这样的不良定义的集合出现。所以“禁止自包含”就是正则公理的内涵之一。

以上几个公理定义了集合的内涵和外延,以及一个操作(分类)。接下来还需定义集合的并(并集公理)这个操作。这个操作的定义与朴素集合论中的定义略有不同。
这个定义的形式描述有点绕,这里形象的说明就是:一个形如R = {{A},{B},{C}, …}的集合,他的并U R = {A, B, C, …}。
所以,{A, B, C}U{D} = U{{A, B, C},{D}} = {A, B, C, D}
相信读到这里很多lisp玩家已经感到似曾相识了。确实函数式语言、图灵机等等计算机科学基本概念都可以说是自集合论派生出来。这也是我会忽然跳这个大坑的原因。

定义了并之后,集合操作基本定义完全。至于说交集这个定义呢,因为存在分类公理,交集其实是分类公理使用“属于某个集合”这个谓词的一种特化形式。因此无需使用公理定义出来。
还有几个公理,我这里配合描述集合论构造自然数的定义来描述。因为这一段似乎是集合论中最容易懂的一部分了

这里直接给出定义的形式,他比描述还容易懂。之后根据这个形式中出现的几个特征,引出几个定义。

0 = {} (空集)
1 = {0} = { {} }
2 = {0,1} = { {}, { {} } }
3 = {0,1,2} = {{}, { {} }, { {}, { {} } }}
4 = {0,1,2,3} = { {}, { {} }, { {}, { {} } }, {{}, { {} }, { {}, { {} } }} }

由于自然数是无穷的,必须使用归纳法定义全体自然数。前面集合的并集的定义是一个铺垫:
首先 0 = {} 是自然数。
然后 假设x是自然数,则x的下一个自然数x’ = x U {x}

由是自然推出了元素的“后继”这个概念。x’是x的后继,则x’ = x U {x}。并依据后继这个概念定义归纳法(无穷公理):
存在集合A,首先,空集属于A,并且对于任意的元素x属于A,则x的后继x’ = x U {x}也属于A。
这不破坏正则公理,因为空集属于A。因此A中存在一个元素,{},他与A的交集是空。
这并不是文字游戏。要求空集包含于A是非常重要的。这相当于是数学归纳法中的初始条件。否则归纳法无法成立。

此外还引出了集合中元素的顺序问题。称x’是x的后继,就是说,x和x’之间存在顺序关系。并且这个顺序关系可以传导。也就是说,x1是x0的后继,x2是x1的后继,则x0和x2也存在x0在前, x2在后这样的关系。 通过这个关系,可以得出集合的序的概念。

首先定义良序集合。所谓良序集合,通俗地说,需要符合两个条件。其一,首先给定一个判定关系的运算符,任意举出这个集合中的两个元素,在这个运算符上能够判定顺序关系(因此这是一个线性的顺序关系,每个元素如果有,都仅有一个后继和一个前驱);其二,任意给定这个集合的子集,能够找出该子集的最小元素(无需保证最大元素)。符合这个条件的集合和这个关系一起就称作良序集。因此自然数在<=这个关系上是良序的。但是整数在<=这个关系上就不是良序。因为不存在这个集合的最小元素。但是如果用其他的顺序关系运算,仍然可以给出一个良序的整数集。例如0, -1, -2, ..., 1, 2, ... 这个顺序,0最小,然后负数比整数小,然后绝对值小的数更小,则得到一个良序集。为何一定要确保最小值,其实就是为了使归纳法成立。后续的大量论证将会建立在良序集的归纳法之上(超限归纳法)。

好了,对于良序集,既然后继是唯一确定的,就可以不停地向后列举下一个后继。如果这个集合是有限集,则最后一个后继的序号(序数)就是一个自然数。如果是可数无穷集合,则这个最大的序数无法在有限时间内获取到。我们设这个数为ω (自然数的序数)。仍然存在序数比ω还大的可数无限集合。例如整数,假如按照上述排序方式,则其序数相当于是ω+ω。又比如,自然数如果采取另一种排序方式0, 3, 6, 9, …, 1, 4, 7, …, 2, 5, 8, …则自然数的序数是ω+ω+ω。我们看到,序作为衡量集合大小的指标有一个不足,就是序是由一个人为确定的关系运算符来确定的。关系不同,就有可能改变序大小。因此还需要更通用的,仅与集合本身相关的指标来衡量集合的大小。

接下来应该引入基数的概念。但由于这一块我还没有完全理解,所以暂且写到这里了。

后续会写一篇文章记录读到目前为止我的各种疑问。作为思考题,推动自己接下来继续去学习~
引用:
维基百科:公理化集合论
刘未鹏:永恒的金色对角线
Matrix67:对角线方法之后的故事

复合功能就是代码坏味

这句话要反复重复给自己听。
今天在看java的官方文档的时候看到了这么一个例子:


List suits = ...;
List ranks = ...;
List sortedDeck = new ArrayList();

// BROKEN - throws NoSuchElementException!
for (Iterator i = suits.iterator(); i.hasNext(); )
    for (Iterator j = ranks.iterator(); j.hasNext(); )
        sortedDeck.add(new Card(i.next(), j.next()));

文档中举这个例子是为了推广foreach语句的使用。但在我看来,之所以容易产生这样的错误,原因是java的iterator的方法不够内聚。

为什么要用iterator.next()返回iterator指向的值?

这让我想起了C++的STL中一系列关于堆栈的操作。获取栈顶的方法是back()或front(),出栈的方法是pop_front()或pop_back()。是这样做,而不是令pop_back()直接返回栈顶值同时弹出。这就保证了函数方法的绝对内聚。从而避免了如同前例那样的潜在错误。

永远保证自己的函数功能单一,没有副作用。不要用小技巧去让一个函数多功能化。有时候潜在的错误远远不是一眼能看得出来的。

搞清C++虚继承

c++虚继承平时很少用到,看书的时候也就是理解个大概。
这次实践中使用到了虚继承,总算有了具体的印象。

基类thunk怎么在子类对象里堆叠的,咱就不赘述了,反正网上、教科书上到处都有讲。这次咱自己遇到的问题主要是虚继承和普通多重继承没搞清区别。

我有一套虚接口hierarchy.


class View {
   virtual int ID() = 0;
   virtual int Child() = 0;
};

class TextView : public {
   virtual const char* getText() = 0;
};

此处省去代码数百行。。。

之后当我开始做这套虚接口的一组实现时,才发现问题。


class SysView : public View {
    virtual int ID();
    virtual int Child();
};

到这里还是都正常的。


class SysTextView : public SysView, public TextView
{
     virtual const char* getText();
};

当实现这个类时,就会发现其对象无法实例化。因为View下面的虚接口没有实现。咦,不是继承了SysView而SysView实现了View下面的所有虚接口吗?问题没有这么简单。因为这个是多重继承,每个父类都会在子类中产生一个拷贝,所以SysTextView里面就出现了两个View类型的父对象。一个是SysView,一个是TextView。而TextView没有实现View的虚接口,因此这个类整个就都不能实例化了。

解决方案就是必须使用虚继承,让编译器知道,TextView继承的那个View和SysView继承的那个View,是同一个View。则需要修改的代码如下:


class SysView: virtual public View {
///...
};

class TextView : virtual public View {
///...
};

注意SysTextView并不需要对其两个父类进行虚继承,除非hierarchy下面还有对这个类进行多继承的情况出现。

虚继承会影响系统性能,应尽量避免。这里用到这个逻辑,只是为了能够使用工厂方法较为优美地创建出各种不同类型的对象,然后调用其虚接口完成操作。并不是性能瓶颈。呜长久以来一个知识漏洞终于通过一次实践补上了。挺好。

Symbian 测试框架调研笔记

symbian没有合适的自动测试框架。不是java的东西就是麻烦。

摘自Nokia wiki, RWindow::Construct

Construct ( const RWindowTreeNode &, TUint32 )

IMPORT_C TInt Construct ( const RWindowTreeNode & parent,
TUint32 aHandle
)

Completes the construction of the window handle.

This method should be called after the RWindow() constructor, before any other functions are performed on the window. It creates a window in the window server corresponding to the RWindow object. The window is initialised to inherit the size and extent of its parent window, given by the first parameter. If its parent is a group window then it will be full screen.

This function always causes a flush of the window server buffer.

 

Parameter Description
parent The window’s parent.
aHandle Client handle for the window. This is an integer value chosen by the client that must be unique within the current server session. The usual way of doing this is to cast the address of the object that owns the window to a TUint32; this allows event handlers which are given a window handle to obtain a reference to the window an event is intended for. For example, CCoeControl uses this technique when it constructs a window. Note that in GUI applications, every window is created and owned by a control. Therefore it is rare for 3rd party code to ever need to call a window’s Construct() function directly.

Returns: KErrNone if successful, otherwise one of the system-wide error codes.

这里的解释很重要,The usual way of doing this is to cast the address of the object that owns the window to a TUint32; this allows event handlers which are given a window handle to obtain a reference to the window an event is intended for. For example, CCoeControl uses this technique when it constructs a window. Note that in GUI applications, every window is created and owned by a control. 也就是说,RWindow的handle可以立即强转为一个CCoeControl*,并且就是构造他的控件对象。

摘自Nokia wiki, RWindowTreeNode::ClientHandle()

ClientHandle ( )

IMPORT_C TUint32 ClientHandle ( ) const

Gets the window’s client handle

The return value is the client’s integer handle that was passed as an argument to the window’s Construct() function: see RWindow::Construct() for a description of the client handle.

This function always causes a flush of the window server buffer.

 

See also: RWindow::Construct()

Returns: Handle ID for the window.

也就是说,通过这个函数可以获取到对应的控件指针。

摘自Nokia wiki, RWindowTreeNode::Child()

Child ( )

IMPORT_C TUint32 Child ( ) const

Gets the first child of the node.

This function always causes a flush of the window server buffer.

 

Returns: The client handle of the child node that currently has ordinal position 0. This is 0 if there isn’t a child.

获取TreeNode的孩子节点。使用NextSibling继续向下遍历。

注意到RWindowGroup也是继承自RWindowTreeNode的。一个测试思路就是,获取到当前WindowGroup,然后遍历整棵树,获取每个节点对应的CCoeControl。暂时不知道Symbian系统上能不能做Dynamic Cast。理论上来说配合RTTI可以了解到每个节点的类型,并对特定的类型调用特定的方法进一步处理。不过跨进程做这种事情恐怕还是有问题。不知能不能把测试程序作为一个dll植入被测程序去运行。这就可以实现Android上Hierarchy Viewer的功能了。

简单记录以作备忘。哪位兄弟知道成熟的symbian测试框架也请不吝赐教。

windows UNC path

原来windows可以支持这种UNC的路径名,甚至用fopen的时候也可以用UNC。全称叫做Uniform Naming Convention.

UNC就是在路径名前面加上\\?\符号。虽然在资源管理器里面不能用,但在浏览器的地址栏里面输入这种类型的路径是可以访问的。例如 \\?\D:\ 之类。

某些时候挺有用,例如路径名长度超过249的时候,或者要访问samba服务器上的资源的时候。做个记录。

Symbian 系统学习-活动对象和其他

最近做了一个android monkey test到symbian的移植。算是草草学习了一下symbian系统吧。写一些东西做个记录,方便以后自己查阅。

symbian系统最有意思的设计应该是这个Active Object的设计模型了。他使用非抢占的多任务模型,固定优先级。这个非抢占我一开始理解得不够好。后来做monkey的时候,用的console程序。想在里面用CActive做进程监视,发现他的RunL函数永远都无法进入。跟公司的symbian大牛也讨论了好久都没有解决问题。最后还是自己悟了。这个所谓的非抢占,就是说你console的主函数不退出的话,这个Active scheduler是不会调度的。之所以那位大牛写的demo可以运行起来,是因为他用的是窗口工程,在一个窗口回调函数里面调用的SetActive。窗口回调函数退出之后自然而然Active scheduler就会调度了。而且估计这个窗口消息的处理,symbian也是采用Active Object来实现的。

我本来以为,只要我在主函数里面调用一个引发调度的函数就好了。我想当然地调用了sleep函数,然完全没有效果。还有后来改用User::After函数,也完全没有用。我后来想,论坛上经常有人提醒说,这个User::After函数是同步的。究竟有多同步,这我才知道了。就是说他也是不会引发AO调度的。这样一想也是可以理解的。因为他说的是非抢占嘛,就是说如果你睡眠了把CPU让给别人,等你到了该睡醒的时间,别人正在运行,那你就没有机会再把这个CPU抢占回来了。假如说做成双方都可以互相协作让CPU的,那基本上就算是协程了。这里的AO,如同网上一些讨论一样,应该算是“纤程”吧,可惜还没腾出时间去仔细研究他。本来想仔细去研究一下AO调度,究竟哪些函数(确切的说应该是系统调用)是可以引发AO调度的,但毕竟还是公司的任务优先吧,这个没有去细查,用了另外一个进程,乖乖写成窗口程序来做了。有机会的话以后再来改吧。

Symbian另外一个有意思的设定就是process崩溃之后不会立即退出。而是会继续保留一段时间,之后系统会把这些死进程统一回收。一开始还不是很理解他为什么要这么做,可是后来做进程监视的时候就发现,这个设定非常好用。你可以在进程崩溃之后,再去遍历系统的进程表,然后找到有问题的进程(他的退出码会是负数),然后找到具体的错误原因。其实这个工作就叫做“验尸”,而且symbian也提供了一些验尸工具去帮助调试。这样就给了系统调试很大的自由空间。否则的话,必须在一开始就确定监视哪些进程。假如有些进程,例如我现在在调试的这个(哔—),他是随着用户操作过程中而时而启动时而退出的,你很难去一直捕获着他,去监视他的运行情况。

还有一个就是本来对于symbian程序员来说是司空见惯的东西吧,就是这个清除栈CleanupStack,还有对象的所谓二次构造,以及symbian自有的一套类似于try catch的机制,是用的Leave和trap来实现。这些东西应该就是symbian的系统特点了吧,其他系统上是绝对见不到这些东西的。从异常安全的角度来说,这个二次构造真的是合理的设计吗?我觉得也不见得。不过他配合清除栈的使用,确实能够保证内存泄漏。这在内存紧缺同时开机时间甚至可能长达数年的嵌入式产品来说,的的确确是至关重要的。否则即使是非常微小的内存泄漏,假如连续开机数年(这里肯定不是手机啦,比如说是电冰箱咖啡壶什么的),还是会引起系统崩溃的。

最后就是监视到进程崩溃如何获取调用栈信息的问题。本来的想法甚至是去获取CPU寄存器的值,然后根据可以得到的线程列表中获取到的堆栈地址的信息,配合编译过程中生成的.exe.map,进行函数地址对比,从而得出调用信息。后来发现这种事情不用自己去做(而且在用户这个层次应该是很难获取到CPU寄存器的值的吧)。symbian提供了一个很好的内核调试工具,d_exc.exe,可以在系统中有任何进程崩溃时记录调用栈信息。需要的只是去把这个程序在测试开始之前运行起来就好了。至于symbian的内核端调试,nokia wiki上有一篇很好的文章,我还没找时间好好研究,这里也先记在这里kernel side debug

好啦今天的笔记就做到这里。并非是API介绍,假如说有人有兴趣想知道具体的内容的话,这里特别推荐的是nokia官方论坛的wiki,内容非常充实,对于我这样的初学者来说,非常有帮助。
例如这篇process monitor,还有这篇process and threads, how to find them等等。

空的xls文件反而更大

有没有哪位对微软office文件格式有研究的?

最近发现个有趣的事情,新建的xls文件有15.5KB,而假如打开,往里面写一个数据,则这个文件就会变成13.5KB。
这是为什么呢?新建的文件反而更大?

<编辑>
又做了几个实验。发现如果打开office excel然后新建-保存,则得到的将会是13.5KB的文件。
但是,如果直接在文件夹或者桌面右键-新建-xls文件,则会得到15.5KB的文件。

同样的实验在office 2007上也尝试了一下,同样右键新建的xlsx文件会更大,有9.64KB,而在程序里新建-保存的xlsx文件只有7.8KB。写入一个数据a之后,文件大小变成8.16KB。但这次不同的是,向9.64KB的文件里写入一个数据a,文件大小不会减小了,而是增加到10KB。两个文件的大小增幅都是0.36KB,这里还是应该可以看出某种规律的。

另外,对office word的测试则有另外的结果。桌面右击新建的word文件为0字节。而新建-保存的word文件为9.89KB。

</编辑>

windows下不能创建名称为con的文件

今天偶然发现的,我想创建一个叫做con.txt的文件来保存数据库连接字符串,居然提示我“指定的设备名无效”。
网上查了查,原来con是windows的控制台设备描述符,所以被保留来不能创建这个名字的文件。
同样道理,例如com1之类名称的文件也都是不能创建的。

当然,你也可以想办法创建出这样的文件,方法网上有很多,这里不说了。而且创建出来之后,就无法简单地删除掉了。(当然想要删,方法也还是有的,网上也同样有很多这里不解释了)

协程的C实现

今天正好跟ownwaterloo聊到协程,于是查了查资料,顺便写个博客记录一下吧。

我主要参考的是这篇资料http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html,是Simon Tatham提出的一个协程的C实现,非常有意思。

协程的思想主要是为了解决多个任务如何分享CPU这个问题的。线程在很多协作问题上处理的不好,而且需要锁机制,导致运行缓慢,不易理解,容易出错。协程的思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制(或者说他本身就是一个全局锁)。

Simon的举例是一个生产者消费者例子。传统的程序可能是生产者一个循环不断产生字符,之后退出。而消费者一个循环不断读取字符,并处理之。使用时或许会用一个管道,将生产者的输出重定向到消费者的输入。从而使两者协作。

Simon提出,如何使这样的简单任务无需通过管道这类操作系统相关的重型机制,就能完成。他提出,生产者或者消费者之间,有一方需要改变自己的工作模式。不再是在循环中不断处理任务,而是一次只处理一个字符然后立刻返回。而另一方依旧保持原状,只需在原本输出到或读取自标准IO的地方修改为调用对方的那个函数就可以了。

但这种写法难以维护。他提出了协程的概念,并期待写出类似这样的代码:

int function(void) {
    int i;
    for (i = 0; i < 10; i++)
        return i;   /* won't work, but wouldn't it be nice */
}

在这里return语句并不是终止函数,而是将函数转入睡眠的状态,并将CPU交付给另一个函数执行。例如生产者写入一个字符后,调用类似的return语句,交付消费者处理这个字符。稍后当消费者处理完并调用return语句后,能重新激活生产者协程,并继续这个for循环。

如何在C语言中实现这样的功能而不需使用汇编代码去hack堆栈和寄存器呢?他给出的最初的实现是使用goto语句。

int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: goto LABEL0;
        case 1: goto LABEL1;
    }
    LABEL0: /* start of function */
    for (i = 0; i < 10; i++) {
        state = 1; /* so we will come back to LABEL1 */
        return i;
        LABEL1:; /* resume control straight after the return */
    }
}

巧妙的使用静态变量存储函数状态,并使用goto语句来继续for循环。但这种写法不够优美,于是又引入了Duff’s device。

switch (count % 8) {
        case 0:        do {  *to = *from++;
        case 7:              *to = *from++;
        case 6:              *to = *from++;
        case 5:              *to = *from++;
        case 4:              *to = *from++;
        case 3:              *to = *from++;
        case 2:              *to = *from++;
        case 1:              *to = *from++;
                       } while ((count -= 8) > 0);
    }

使用这种trick来将switch-case语句作为循环中的跳转。这就避免了goto-label语句需要同时维护goto和label两处代码的麻烦。于是前面的代码可以变成这个样子。

int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: /* start of function */
        for (i = 0; i < 10; i++) {
            state = 1; /* so we will come back to "case 1" */
            return i;
            case 1:; /* resume control straight after the return */
        }
    }
}

他的文章后面还有一些内容,例如如何使用宏包装这套机制。如何使用__LINE__宏避免state被赋予相同的数值。如何避免多线程调用下干扰静态变量等等。我这里就不赘述了,大家有兴趣可参考原文。
总之读此文的两个收获一个是认识协程,一个是学习到了一种诡谲的C语言用法。非常开心。