计算尺的原理与制作
事情是这样的,我在b站看到了一个古董计算尺的视频,就想着自己能不能搞一把,但是奈何在网上逛了一圈没找到能用的图片,所以只好自己考功名一下他的原理然后写写代码自动生成。
本文是介绍简易计算尺的制作过程,目前只完成了乘除部分。
什么是计算尺
计算尺就是在没有电子计算工具的时代用来辅助计算的一种仪器。
详情可以在这个视频里面看到
【百元科技古董-01】被禁止带入考场的尺子与人类没有计算器之前的工程师帮手
计算尺的原理
计算尺的乘除部分使用的是对数原理,上个学期在数学书上看到的对数原理的应用终于在这里找到了铁证。
计算尺的基本原理是把其他的运算变成加减运算,所以才可以用尺子的平行移动来计算。
简单来说,$lg(x \cdot y) = lg(x) + lg(y)$, 所以,我们可以制作出两把一样的尺子,这两把尺子的每一个刻度长度都是相应数字的$log_{10}$值,且这两把尺子的左端刻度起点都是1,那么我们把上尺的1对准下尺的第一个乘数,然后找到上尺上的另一个乘数的刻度,再把这个刻度啮合的下尺刻度读出,就可以得到结果。
这个过程就是把两个乘数的$log_{10}$值相加,然后用这个$log$值再对应回原乘积
除法是乘法的逆运算,所以计算与原理也很简单。原理就是$lg(\dfrac x y) = lg(x) - lg(y)$.如果要计算除法,那么则需要把上尺的除数对准下尺的被除数刻度,然后读出上尺中的对准了下尺中的1刻度的刻度,这个刻度就是商数。
具体的用法也可以在刚刚我提到的b站视频里面看到。
计算尺的图像生成
svg: 可缩放矢量图形(Scalable Vector Graphics,SVG)基于 XML 标记语言,用于描述二维的矢量图形
考虑到svg作为纯文本(代码)的图像格式比较好生成,所以我就选用svg生成。
一张简单的svg图像的代码大概这样:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="2977.2px" height="4208.4px" viewBox="0 0 2977.2 4208.4"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
<line x1="0" y1="0" x2="200" y2="200"
style="stroke:rgb(255,0,0);stroke-width:2"/>
</svg>
使用python语言进行开发,不导入第三方库了因为要考功名很麻烦,我就用一点点功能,所以直接使用print的方法就好了。
import math as m
# svg 前缀
_svg = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="4208.4px" height="2977.2px" viewBox="0 0 2977.2 4208.4"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
"""
#svg 后缀
svg_ = """
</svg>
"""
print(_svg)
#参数设置
rate = 2100
end_num = 500
xMove = -1340
yMove = 60
# 画上尺!!
for a in range(1, end_num + 1):
# 根据对数原理计算刻度的位置
lgi = m.log(a, 10) * rate
# 划出有刻度数值的标识的较长的线和数字
if ((a % 5 == 0 or a <= 10) and a <= end_num // 2) or (a % 10 == 0 and a > end_num // 2):
textY = yMove + 16 if (a > end_num // 2 and a % 20 == 0) or (a <= end_num // 2 and a % 10 == 0) else yMove + 30
print('<text x="%d" y="%d" fill="black" font-size="20">%d</text>' %(lgi + xMove, textY, a))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 80))
# 划出短标识
else:
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove + 50, lgi + xMove, yMove + 80))
# 划出尺子的上下边缘
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove, m.log(end_num, 10) * rate + xMove, yMove))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove + 80, m.log(end_num, 10) * rate + xMove, yMove + 80))
# 画上尺结束
yMove += 160
# 画下尺!!
for a in range(1, end_num + 1):
# 根据对数原理计算刻度的位置
lgi = m.log(a, 10) * rate
# 划出有刻度数值的标识的较长的线和数字
if ((a % 5 == 0 or a <= 10) and a <= end_num // 2) or (a % 10 == 0 and a > end_num // 2):
textY = yMove + 64 if (a > end_num // 2 and a % 20 == 0) or (a <= end_num // 2 and a % 10 == 0) else yMove + 78
print('<text x="%d" y="%d" fill="black" font-size="20">%d</text>' %(lgi + xMove, textY, a))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 80))
# 划出短标识
else:
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 50))
# 划出尺子的上下边缘
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove, m.log(end_num, 10) * rate + xMove, yMove))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove + 80, m.log(end_num, 10) * rate + xMove, yMove + 80))
# 画下尺结束
print(svg_)
然后把这个svg文件打开做个微调就可以用来打印了😃
程序的使用
python3 calc_ruler.py > output.svg
你也可以通过调整注释:参数设置下面的那几行代码中的值来更改你想要的尺子的范围、大小、缩放等等。
最终效果

计算尺的改进
因为1刻度里面的空隙很大(也即log函数的特性),所以我们可以将里面填满小刻度。
代码如下
import math as m
# svg 前缀
_svg = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="8000px" height="5656.85424949238019521351px" viewBox="0 0 8000 5656.85424949238019521351"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
"""
#svg 后缀
svg_ = """
</svg>
"""
print(_svg)
#参数设置
rate = 2500
end_num = 500
small_end_num = 25
xMove = 0
yMove = 60
# 画上尺!!
# 划出整数刻度位置
for a in range(1, end_num + 1):
# 根据对数原理计算刻度的位置
lgi = m.log(a, 10) * rate
# 划出有刻度数值的标识的较长的线和数字
if ((a % 5 == 0 or a <= 10) and a <= end_num // 2) or (a % 10 == 0 and a > end_num // 2):
textY = yMove + 16 if (a > end_num // 2 and a % 20 == 0) or (a <= end_num // 2 and a % 10 == 0) else yMove + 30
print('<text x="%d" y="%d" fill="black" font-size="20">%d</text>' %(lgi + xMove, textY, a))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 80))
# 划出短标识
else:
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove + 50, lgi + xMove, yMove + 80))
# 划出尺子的上下边缘
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove, m.log(end_num, 10) * rate + xMove, yMove))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove + 80, m.log(end_num, 10) * rate + xMove, yMove + 80))
# 划出小数刻度位置
for a in range(10, small_end_num * 10 + 1):
lgi = m.log(a / 10, 10) * rate
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove + (30 if a % 5 == 0 else 40), lgi + xMove, yMove + 80))
# 画上尺结束
yMove += 160
# 画下尺!!
for a in range(1, end_num + 1):
# 根据对数原理计算刻度的位置
lgi = m.log(a, 10) * rate
# 划出有刻度数值的标识的较长的线和数字
if ((a % 5 == 0 or a <= 10) and a <= end_num // 2) or (a % 10 == 0 and a > end_num // 2):
textY = yMove + 64 if (a > end_num // 2 and a % 20 == 0) or (a <= end_num // 2 and a % 10 == 0) else yMove + 78
print('<text x="%d" y="%d" fill="black" font-size="20">%d</text>' %(lgi + xMove, textY, a))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 80))
# 划出短标识
else:
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 50))
# 划出尺子的上下边缘
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove, m.log(end_num, 10) * rate + xMove, yMove))
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove + 80, m.log(end_num, 10) * rate + xMove, yMove + 80))
# 划出小数刻度位置
for a in range(10, small_end_num * 10 + 1):
lgi = m.log(a / 10, 10) * rate
print('<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + (40 if a % 5 == 0 else 30)))
# 画下尺结束
print(svg_)
那么这就可以生成一张带有刻度的svg图像了。

然后使用inkscape软件微调,复制几份用来使用A4打印机打印,再裁纸。

其他的运算我以后可能也会做。
2023-06-06 小核桃
2023-06-07 更新
踩坑记录
之前的代码里面有bug, 上尺的刻度线画错了,整数厘米的地方和0.5厘米的地方没有区分开来,所以导致测量时候会令人困惑。
之前的代码里面,我输出的时候用的是%d,但是这里的刻度为了精确,应该使用双精度浮点型,也即double的%Lf来输出,然而%d只能用来输出整数,所以导致了踩坑。而且使用的格式化输出并不会导致代码更简洁。
使用%d的后果就是后面的小数被四舍五入掉了,所以到后面接近500的刻度线的时候有些刻度会(精确的)叠在一起,我很早就发现了这个问题,但是直到现在我才发现这个问题的解决办法。
这个踩坑给我一个教训,也就是输出格式化字符串的时候最好用f"string{var_name}"来输出,虽然这是一个很晚才支持的特性,但是这可以使代码更清晰和少犯错。
现在我把修改过的代码贴上来
import math as m
# svg 前缀
_svg = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="8000px" height="5656.85424949238019521351px" viewBox="0 0 8000 5656.85424949238019521351"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
"""
#svg 后缀
svg_ = """
</svg>
"""
print(_svg)
#参数设置
rate = 2500
end_num = 500
small_end_num = 25
xMove = 0
yMove = 60
# 画上尺!!
# 划出整数刻度位置
for a in range(1, end_num + 1):
# 根据对数原理计算刻度的位置
lgi = m.log(a, 10) * rate
# 划出有刻度数值的标识的较长的线和数字
if ((a % 5 == 0 or a <= 10) and a <= end_num // 2) or (a % 10 == 0 and a > end_num // 2):
textY = yMove + 16 if (a > end_num // 2 and a % 20 == 0) or (a <= end_num // 2 and a % 10 == 0) else yMove + 30
print('<text x="%Lf" y="%Lf" fill="black" font-size="20">%d</text>' %(lgi + xMove, textY, a))
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 80))
# 划出短标识
else:
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove + 30, lgi + xMove, yMove + 80))
# 划出尺子的上下边缘
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove, m.log(end_num, 10) * rate + xMove, yMove))
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove + 80, m.log(end_num, 10) * rate + xMove, yMove + 80))
# 划出小数刻度位置
for a in range(10, small_end_num * 10 + 1):
lgi = m.log(a / 10, 10) * rate
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove + (40 if a % 5 == 0 else 50), lgi + xMove, yMove + 80))
# 画上尺结束
yMove += 160
# 画下尺!!
for a in range(1, end_num + 1):
# 根据对数原理计算刻度的位置
lgi = m.log(a, 10) * rate
# 划出有刻度数值的标识的较长的线和数字
if ((a % 5 == 0 or a <= 10) and a <= end_num // 2) or (a % 10 == 0 and a > end_num // 2):
textY = yMove + 64 if (a > end_num // 2 and a % 20 == 0) or (a <= end_num // 2 and a % 10 == 0) else yMove + 78
print('<text x="%Lf" y="%Lf" fill="black" font-size="20">%d</text>' %(lgi + xMove, textY, a))
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 80))
# 划出短标识
else:
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + 50))
# 划出尺子的上下边缘
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove, m.log(end_num, 10) * rate + xMove, yMove))
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (m.log(1, 10) * rate + xMove, yMove + 80, m.log(end_num, 10) * rate + xMove, yMove + 80))
# 划出小数刻度位置
for a in range(10, small_end_num * 10 + 1):
lgi = m.log(a / 10, 10) * rate
print('<line x1="%Lf" y1="%Lf" x2="%Lf" y2="%Lf" style="stroke:black;stroke-width:1"/>' % (lgi + xMove, yMove, lgi + xMove, yMove + (40 if a % 5 == 0 else 30)))
# 画下尺结束
print(svg_)
那么这就是新的代码,然后还有新的图片

打印用的图片
