科技文献中常见的数词来源

之前经常在科技文献中看到这些词的使用:

  • 表示数进制的:bin、oct、dec、hex ...
  • 表示参数个数的:nullary、unary、binary、ternay ...
  • 表示倍数的:single、double、triple、quadruple ...
  • 表示优先级的:primary、secondary、tertiary ...
  • 表示几何形状的:triangle、tetragon、pentagon、hexagon ...

一直以来却不甚知道为什么用这些词来表达,其含义究竟是什么?最近研究了一番才了解到了一些端倪。大体上是因为现代科学基本上是从古希腊到古罗马一系传承下来的,其用语自然受到希腊语和拉丁语的影响,且由于各个学科门类的出现时间和跨度相差很大,往往会出现多种语源的词汇混合在一起使用的情形。

古希腊人爱好几何学研究,可以猜到几何形状的描述肯定是希腊词根了。而其他很多场景下用的则是拉丁词根或希腊/拉丁组合词根。下面是摘抄自 wikipedia 的一些数字词根:

  • 希腊语:
数值 表基数 表倍数
1/2 hemi- -
1 hen- mono-, haplo-
2 di-, dy-, duo- dis-
3 tri- tris-,
4 tetra- tetrakis-
5 penta- pentakis-
6 hexa- hexakis-
7 hepta- heptakis-
8 ogdo-, octa-, octo- octakis-
9 ennea- enneakis-
10 deca- decakis-
16 hexakaideca-, hexadeca- hexadecakis-
  • 拉丁语:
数值 表基数 表倍数 表分配(即每多少个的意思) 表序数(即第多少个的意思)
1/2 semi- - demi- -
1 uni- sim- singul- prim-
2 du- bi-, bis- bin- second-
3 tri- ter- tern-, trin- terti-
4 quadri-, quadru- quater- quatern- quart-
5 quinque- - quin- quint-
6 sexa- - sen- sext-
7 septem-, septi- septem-, septi- septen- septim-
8 octo- - octon- octav-
9 novem- novem- noven- nona-
10 decem-, dec- decem-, dec- den- decim-
16 sedec-, sexdec- sedec-, sexdec- sedec-, sexdec- sedec-, sexdec-

从拉丁语词根里是不是看到了一些熟悉的东西?呵呵,电影里经常看到的半神 demigod 的 demi- 就是一半的意思,而英语里表达月份的名词实际上就是罗马人用拉丁语数第几个月了,不过个别月份为了纪念神明、节期或皇帝而变更了名称,且原始的罗马历法中一年只有 10 个月,现在的 3 月是当时的第一个月,12 月则是第十个月,漫长的冬日则认为无需计算月份,后来变更历法时才加入现在的 1 月和 2 月。各个月份名的含义如下:

月份名称 含义
January 取自罗马神话中门神 Janus,表达一年开始的意思
February 取自拉丁语 februum (即净化),因为这段时间内会举行传统的净化仪式
March 取自罗马神话中战神 Mars
April 可能源自拉丁语 aperire (即开放),暗示这个季节树木花草都开始发芽,也可能取自希腊神话中美神 Aphrodite
May 取自希腊神话中女神 Maia
June 取自罗马神话中女神 Juno
July 本来叫 Quintillis (即第 5 个月,词根 quin-),后为纪念皇帝 Julius Caesar 改名
August 本来叫 Sextillis (即第 6 个月,词根 sexa-),后为纪念皇帝 Augustus 改名
September 即第 7 个月 (词根 septem-)
October 即第 8 个月 (词根 octo-)
November 即第 9 个月 (词根 novem-)
December 即第 10 个月 (词根 decem-)

而计算机中常见的 NULL 或 NIL 则来自拉丁语 nulla 和 nil (表示什么都没有的意思),不过 nil 在希腊语中也表达类似的意思。表达 16 进制的单词 hexadecimal 比较奇葩,是一个希腊/拉丁混合词,以前有人争论说应该改成纯拉丁词 senidenary 或 sedenary,后来也没能挡住习惯的力量。

参考:

如何将 Python 模块打成 DEB/RPM 包

Python 模块的安装可以用 easy_install 或 pip 方便地完成,但此类工具难以应用在生产系统的部署中。使用 DEB/RPM 包的好处是大规模部署简单、容易回滚且能以一致的方式管理依赖,所以需要将 Python 模块打成此类原生包。

打 RPM 包比较简单,现在的模块一般都是 setuptools 规范的,只要简单地进入模块源码目录,运行 python setup.py bdist_rpm 即可打出 RPM 包。

打 DEB 包需要 stdeb 工具的支持,安装该工具后可以用如下命令打出 DEB 包:

  • 下载要打的模块(假设为 uncertainties 模块)源码 tarball,可用命令 pip install -d . uncertainties
  • 假设源码包为 uncertainties-2.4.1.tar.gz ,执行 py2dsc uncertainties-2.4.1.tar.gz 将其转换为 Debian 源码包
  • 进入 py2dsc 生成的打包目录打包:
    • cd deb_dist/uncertainties-2.4.1/
    • dpkg-buildpackage -rfakeroot -uc -us
  • 生成的 DEB 包就在上级目录里

如果只是想直接安装转换后的 DEB 包,则可以简单地用命令 sudo pypi-install uncertainties 完成,无需使用上面较麻烦的步骤。

参考:

org2blog 如何发布左对齐的 LaTeX 公式

通常情况下 Wordpress 上的基于 MathJax 显示的 LaTeX 插件总是将独立公式显示为居中对齐的,但当文章中的公式都比较短时居中对齐就显得不太美观。若想让独立公式在文章中总是左对齐,可以在编辑 org2blog 内容时在正文开始前加入如下片段:

1
2
3
4
5
6
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
displayAlign: "left",
displayIndent: "2em"
});
</script>

这里的 displayIndent 可以控制左对齐公式的缩进宽度,若设为 "0" 就表示同正文平齐。下面是具体的例子:

abxdx\int_a^b x\,dx

f(x)=+esxdsf(x)=\int_{-\infty}^{+\infty}\mathrm{e}^s x\,\mathrm{d}s

P.S. 虽然 MathJax 支持直接识别很多 LaTeX 的环境块,但通常 Wordpress 上的 LaTeX 插件仅在检测到自己识别的 LaTeX 标签时才会生成加载 MathJax 的代码,如果 blog 中只使用了 LaTeX 环境块就会导致无法加载 MathJax 而全部显示为普通文本。解决方法是在正文某处显式写一个空内容的可识别 LaTeX 标签欺骗 LaTeX 插件加载 MathJax。

参考:https://github.com/mathjax/mathjax-docs/wiki/MathML-alignment-Left-or-Right

org2blog 发布语法高亮源码块的注意事项

Org2blog 要在 Wordpress 中发布语法高亮的源码区块,需要做以下准备:

  • Wordpress 中要安装 SyntaxHighlighter Evolved 插件
  • emacs 中添加设置以将 pre 区块转换为 sourcecode 区块以让语法高亮插件工作: (setq org2blog/wp-use-sourcecode-shortcode t)

最后,org2blog 依赖于 org-mode 的 HTML 导出功能,后者总是会转义所有 XML/HTML 特殊字符,但当前版本的 SyntaxHighlighter Evolved 插件也总是会转义这些符号,造成双重转义使高亮输出发生混乱。解决方式是修改 SyntaxHighlighter Evolved 插件,将 syntaxhighlighter.php 中的 encode_shortcode_contents_callback() 函数改为如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
function encode_shortcode_contents_callback( $atts, $code = '', $tag = false ) {
$this->encoded = true;
// XXX: modified by wxz
if (false === strpos($code, '<') && false === strpos($code, '>')) {
// content already escaped
} else {
// content not escaped yet
$code = str_replace( array_keys($this->specialchars), array_values($this->specialchars), htmlspecialchars( $code ) );
}
// XXX: ends here
return '[' . $tag . $this->atts2string( $atts ) . "]{$code}[/$tag]";
}

这样可以在源码区块内容已经转义的情况下不再进行二次转义,从而解决输出混乱问题。

下面是一个语法高亮的例子:

1
2
3
4
5
6
#include <stdio.h>
int main(int argc, char *argv[])
{
printf ("Hello, world!\n");
return 0;
}

编译 Java 程序时出现 code too large 错误的分析

我参与的一个实际项目里用到了 ANTLR 3.x 进行 parser 代码自动生成,其中含有大量的 static field 用作状态转移查找表。由于一个 class 中所有 static field 都是放在一个匿名 method 中统一进行初始化,而 Java VM 规范规定一个 class 中单个 method 中 bytecode 长度最多不能超过 65535 bytes,这个自动生成的 parser class 始终无法在 javac 上编译,总是会提示 error: code too large 。但问题是在 Eclipse 中编译使用该文件完全没有问题,由于 65535 bytes 的限制是 Java VM 规范所规定,同生成 class 的编译器没有关系,因此原因只可能是 ecj 生成的 bytecode 比 javac 少,这就有点儿诡异了。

要查看 class 中的字节码可以用 javap 程序完成,通过下面的命令可以将有问题的 class (这里假设是 Test)的静态初始化匿名 method 中所有 bytecode 输出:

1
javap -c Test | sed -ne '/static {}/,/return/ p'

将 Test class 的 static field 用对分法逐渐裁剪到 javac 刚好能编译过去,然后用上述命令导出 javac 编译结果和 ecj 编译结果进行对比分析,果然发现了一些差异(测试代码可从此处下载)。

例如如下的这段 Java 代码:

1
2
public static final long[][] FOLLOW_DEFAULT_in_table_option26937 =
new long[][]{new long[]{0x0000000000000000L,0x0000000002000000L}};

由 ecj 编译产出的 bytecode 为:

offset bytecode stack [before]->[after]
0 iconst_1 -> 1
1 anewarray #idx count -> arrayref
4 dup value -> value, value
5 iconst_0 -> 0
6 iconst_2 -> 2
7 newarray long count -> arrayref
9 dup value -> value, value
10 iconst_1 -> 1
11 ldc2_w #idx -> value
14 lastore arrayref, index, value ->
15 aastore arrayref, index, value ->
16 pustatic #idx value ->

由 javac 编译产出的 bytecode 为:

offset bytecode stack [before]->[after]
0 iconst_1 -> 1
1 anewarray #idx count -> arrayref
4 dup value -> value, value
5 iconst_0 -> 0
6 iconst_2 -> 2
7 newarray long count -> arrayref
9 dup value -> value, value
10 iconst_0 -> 0
11 iconst_0 -> 0
12 lastore arrayref, index, value ->
13 dup value -> value, value
14 iconst_1 -> 1
15 ldc2_w #idx -> value
18 lastore arrayref, index, value ->
19 aastore arrayref, index, value ->
20 pustatic #idx value ->

可以看到 javac 多生成了 offset 10~13 的 bytecode,分析可知这段代码的功能是将内部 long[] 数组第一个元素置为 0。也就是说,ecj 利用了 Java VM 规范中的默认初始化约定,不会为显式初始化为 0 的元素生成赋值 bytecode,但 javac 却会傻傻地按照初始化列表逐个生成 bytecode。这样当非 0 元素初始化很少时,ecj 就比 javac 生成的静态初始化 bytecode 少得多了,自然会发生 javac 编译报 code too large 错误但 ecj 正常的情况。

不过从另一方面看,ANTLR 3.x 生成的 parser 代码也确实没有考虑 Java VM 的 bytecode 长度限制,一旦语法复杂到一定程度,生成的代码很可能连 ecj 都无法编译通过。这是一个比较严重的设计疏忽,希望 ANTLR 后续版本使用变通方法对这一点进行改进。

参考资料

在使用 Ecipse Compiler 的 Maven 项目中如何使用 Lombok

Lombok 借助 javac 的内部 API 介入 annotation 处理环节,并根据其特有的 annotation 在编译期生成代码,能够大大简化 Java 代码很多冗余的写法。但正是因为其依赖于 javac 内部 API,Lombok 很难在其他 Java 编译器上使用,例如常用的替代编译器 Eclipse Compiler (ecj) 就是如此。

为了在非 javac 编译器上也能享受 Lombok 带来的好处,一个可能的方案是实际编译之前先借助 javac 的内部运行时库对 Java 代码进行 delombok 操作,即将被 Lombok 修改后的 Java 代码导出成实际文件,再使用 Eclipse Compiler 这样的替代编译器对导出代码进行真正的编译操作。方法如下:

  • 先让 Maven 项目在编译前预先执行 delombok 操作,修改 pom.xml 增加如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<build>
...
<!-- 设置 delombok 展开后代码放置位置 -->
<sourceDirectory>target/generated-sources/delombok</sourceDirectory>
<testSourceDirectory>target/generated-test-sources/delombok</testSourceDirectory>
...
<plugins>
...
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>0.11.8.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
</configuration>
</execution>
<execution>
<id>test-delombok</id>
<phase>generate-test-sources</phase>
<goals>
<goal>testDelombok</goal>
</goals>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/test/java</sourceDirectory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>sun.jdk</groupId>
<artifactId>tools</artifactId>
<version>1.6</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</plugin>
...
</plugins>
...
</build>
  • 如果 Maven 项目还没有使用 Eclipse Compiler,则还要修改 pom.xml 以启用它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>UTF-8</encoding>
<compilerId>eclipse</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>1.9.1</version>
</dependency>
</dependencies>
</plugin>
...
</plugins>
...
</build>

经过以上配置之后,Maven 项目的实际和测试 Java 代码中即可正常使用 Lombok annotation,也可以在 mvn compile 之后进入 target/generated-sources/delombok/target/generated-test-sources/delombok/ 目录自行查看被 Lombok 修改后的实际和测试代码。

使用 Terratec Cinergy T Stick+ DVB 电视卡进行 RTL-SDR 实验时的几点注意事项

  • Terratec Cinergy T Stick+ 相关驱动仅在 kernel 3.7 及以上版本中并入了主干
  • 安装 rtl-sdr 工具后,请确保 /etc/udev/rules.d/ 中增加了其给出的 udev 规则以对设备结点权限进行修正,否则只有 root 才能访问 DVB 设备
  • 使用 rtl-sdr 等 SDR 工具前,需要将自动加载的 dvb_usb_rtl28xxu 模块卸载,否则 SDR 相关工具会无法打开 DVB 设备并报错,建议检出最新版 rtl-sdr 代码并使用 --enable-driver-detach 选项配置后编译安装,这样 rtl-sdr 工具一旦发现设备被该模块占用便会将其自动卸载。

对 GNUCash 进行的一点儿改进

当前版本的 GNUCash 在连接 MySQL 数据库时有个比较严重的字符编码问题:为了兼容 Unicode 字符,GNUCash 在 gnc_dbi_mysql_session_begin() 函数中主动执行 SET NAMES 'utf8' 语句设定 MySQL 连接字符编码为 UTF-8,但遗憾的是用来在连接故障时修复连接的 gnc_dbi_verify_conn() 函数并没有进行类似的操作。一旦数据库连接因网络问题意外断开,用户输入新交易时就会使用 gnc_dbi_verify_conn() 重新创建的新连接,而该连接的字符编码是默认的 latin1,直接后果就是输入中文等字符都变成了一堆乱码。

实际上 GNUCash 底层使用的 libdbi 库是考虑了连接字符编码问题的,其专门提供了一个 "encoding" 的设置选项用来对连接字符编码进行通用设置,底层的各个 driver 在创建连接时都会尊重该设置。所以只要简单地增加该选项,不管连接怎么断开重连,字符编码都能保持正常。

打上附件中的 fix_charset.patch 就可以修正此问题了。

另外,General Ledger 功能实际上非常好用,有了它就不需要在各个 Account 之间来回切换查帐了。但 GNUCash 目前默认在 General Ledger 中只显示最近一个月的交易,可苦了想对更老交易对账的用户。

我对其进行了小小的修改,打上附件中的 enh_general_ledger.patch 即可让 General Ledger 显示当前年度的所有交易,这样对账就非常方便了。

更新: 最近发现 General Ledger 实际上可以自定义显示日期范围,只要在进入 General Ledger 界面后选择菜单上的 View/Filter...,即可在弹出对话框的 Date 页选择显示起止日期,无需使用附件中的补丁。

修复 GNUCash 数据库中的乱码

GNUCash 是一款很好的记账软件,但在使用 MySQL 作为存储后端时,由于其对连接字符集的设置有漏洞,容易出现记账备注中的中文变乱码的情况。此时,可以在 MySQL 中执行如下 SQL 语句完成修复工作:

1
UPDATE splits SET memo = CONVERT(BINARY(CONVERT(memo USING latin1)) USING utf8) WHERE CHAR_LENGTH(CONVERT(BINARY(CONVERT(memo USING latin1)) USING utf8)) != CHAR_LENGTH(memo);

在 Mele A2000 上运行 Archlinux ARM 版

本来以为 archlinuxarm.org 上有 Mele A100 的安装指南装起来会方便些,但实际操作过程中仍然踩了一些坑,这里记录一下备查。

我选择 Mele A2000 的原因就是因为有 SATA 口,使用最新的 U-boot 后有可能直接把根分区放在 SATA 盘上,这样比起根分区在 SD 卡或 USB2.0 硬盘启动要快很多。遗憾的是,archlinuxarm.org 上最新的 sun4i 架构 rootfs 中带的 kernel 内置的启动代码有点儿小问题,在新版 Mele A2000 的板子上会将 SATA 控制器时钟设置错误,导致 kernel 启动挂载 sata 盘分区时循环显示 sata 接口 reset 错误无法继续:

1
2
3
4
5
6
7
[ 5.530000] ata1: SATA link down (SStatus 1 SControl 300)
[ 5.530000] ata1: EH complete
[ 5.540000] ata1: exception Emask 0x10 SAct 0x0 SErr 0x4000000 action 0xe frozen
[ 5.540000] ata1: irq_stat 0x00000040, connection status changed
[ 5.550000] ata1: SError: { DevExch }
[ 5.550000] ata1: limiting SATA link speed to 1.5 Gbps
[ 5.560000] ata1: hard resetting link

因此只有下载修正后的 kernel 代码自行编译内置了 sw_ahci_platform 模块的 uImage(以 Ubuntu 11.04 为例):

1
2
3
4
5
6
7
$ sudo apt-get install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi u-boot-tools
$ git clone https://github.com/linux-sunxi/linux-sunxi.git
$ cd linux-sunxi
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- sun4i_defconfig
$ make ARCH=arm menuconfig ,将 Device Drivers/Serial ATA and Parallel ATA drivers/SoftWinner Platform AHCI SATA support 从默认的 module 改为 built-in
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage modules
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- INSTALL_MOD_PATH=output modules_install

然后:

  • arch/arm/boot/uImage 复制到 SD 卡启动 FAT 分区下覆盖从原 rootfs 中复制过来的同名文件
  • output/lib 目录复制到 sata 盘 rootfs 中的 /usr/ 目录下
  • 重新启动应该就能正常从 sata 盘启动了

以上过程参考:

启动之后的另一个问题就是无线网络,由于 archlinuxarm.org 上下载的 rootfs 是最小镜像,连配置网卡所需的 wireless_tools 包都没有装,需要自己编译一个放到 rootfs 里:

1
2
3
4
5
wget http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/wireless_tools.29.tar.gz
tar xzf wireless_tools.29.tar.gz
cd wireless_tools.29
make CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-ld
make install PREFIX=<your root fs mount-point>/usr/local

在你的 rootfs 上新增文件 /etc/ld.so.conf.d/local.conf ,内容只有一行 /usr/local/lib ,保存后运行 ldconfig 刷新动态库缓存

通过 wireless_tools 和 rootfs 中带的 wpa_supplicant 可以手动连接 WPA2 验证的无线网络,过程为:

  • 当前目录新增 wpa_supplicant 配置文件 mine.conf,内容为:
1
2
3
4
5
6
7
8
9
ctrl_interface=/var/run/wpa_supplicant
eapol_version=1
ap_scan=1
fast_reauth=1

network={
ssid="无线接入点名称"
psk="无线接入密码"
}
  • modprobe 8192cu ,这步很重要!默认没有自动加载 rtl8192cu 无线驱动
  • ip link set wlan0 up
  • wpa_supplicant -i wlan0 -c mine.conf -B
  • iwconfig wlan0 essid <无线接入点名称>
  • dhcpcd wlan0 能上网后一切都好说了。

以上过程参考:https://wiki.archlinux.org/index.php/Wireless_Setup#Manual_setup

更新: 目前 archlinuxarm.org 上下载的最新镜像已经修正了 SATA 时钟错误问题并内置了 sw_ahci_platform 模块,无需自行编译内核了。另外 wireless_tools 也不用自己编译了,可以直接下载 安装包 用 pacman 安装