
有趣的“码神挑战”
发布
昨天在B站刷到一位叫“鱼皮”的Up的视频,内容是介绍面向广大程序员发起的“码神挑战”答题活动。想要证明自己的“编程实力”的同学可以去看看,答题的同时巩固一下自己的编程知识,同时收获一份快乐~ 😆
点击此处开始挑战。
P.S.
已通关,学无止境!(全程开发者控制台工具没关过~有几关没解出来,切到移动端视图跳过去的)

给Life平台加上动账消息推送提醒
发布
公司有一个Life平台,登录之后可以查看饭卡的余额和消费记录等数据。但体验下来,觉得这个平台有诸多需要改进的地方:一是Life平台后端的Token有效期非常短,没多久就失效要重新登录;二是前端没有适配移动端,操作不便;三是金额消费和充值没有动账提醒,无法实时查看消费情况。
为了解决上述诸多不便,花时间用Python实现了一个数据实时爬取、更新、统计、动账推送的脚本。利用Cron定时任务,在一般时段设置频率为半小时执行一次数据同步任务,在饭点高峰时段每分钟执行,一旦监测到有新的动账记录,会通过PushBullet将动账相关信息发送到手机上。
目前服务端部署在了一台Root了的随身Wi-Fi上的Linux Deploy里,刚好可以把每个月用不完的流量分担出去一点。


同时,也为随身Wi-Fi的后台写了一个插件,可以手动刷新查看当前的账户资金情况和最近的一条交易记录。

目前唯一的不足:受限于国内厂商系统推送政策,PushBullet需要一直挂在后台运行,才可以收到推送消息;而且,尽管已经设置了PushBullet省电策略为“无限制”了,但切到后台后,仍然有概率会延迟几分钟才可以收到推送通知,打开App后是可以立即接收到的,未来可能需要看看有什么省事的通知替代方案。
关于IEEE 754浮点数标准
发布
IEEE 754(IEEE Standard for Floating-Point Arithmetic,即IEEE二进制浮点数算数标准),定义了表示浮点数的格式(包括-0)与反常值(denormal number),无穷Infinity与非数值(NaN,Not a Number)等数值表示规则和方法 [1]。
二进制浮点数表示法
JS使用IEEE 754双精度64位二进制格式来表示数值类型 [2]。在IEEE 754标准中,一个双精度64位数字包含以下几个部分(从左往右):
sign(符号位,占用1bit),表示这个数是正/负数,0表示正数;1表示负数exponent(指数位,占用11bit),表示将这个浮点数以科学计数法形式展示的指数mantissa(有效数字位,占用52bit),表示这个浮点数以科学计数法形式展示的底数
二进制、十进制互相转换
回顾一下,二进制和十进制互相转换的方法:
二进制转十进制:例如一个二进制整数:01101101,转换为十进制的运算过程如下:
得到 。
十进制转二进制:例如十进制数109,转换为二进制的运算过程如下:
得到 。
十进制转二进制:对于小数例如0.8125,使用“乘2取整法”:
得到
二进制转十进制:对于二进制小数如0.1101:
得到
双精度64位浮点数表示
如果使用IEEE 754标准来表示双精度浮点数数值0.1,可按照如下步骤来手动计算推导:
首先,可以比较简单地知道符号位(Sign)的位的值为0;
接下来,利用“乘2取整法”,将0.1转换为二进制:
得到 ,是一个无限循环小数,循环节为 0011。
然后使用科学计数法,将这串二进制小数写成科学计数法形式:
可以知道,指数位为-4,因为指数部分(Exponent)采用“偏移编码”(目的是为了在二进制中能同时表达正指数和负指数,并且避免使用额外的符号位,还可以保留特殊值),所以实际的指数 (为什么是+1023?因为指数位占用11bit,,中点是1023)。
故:
1019转换为11位的二进制:
接下来是有效数字位(mantissa),即二进制科学计数法的底数 ,取出小数点后52位(为什么只取小数点后的值?因为在科学计数法下,只保留小数点前1位,而在二进制形式下小数点前的那一位永远会是1,所以可以直接直接忽略)。
所以:
最终,我们得到0.1的双精度64位浮点数每位的值:
| S | E | M |
|---|---|---|
| 0 | 01111111011 | 1001100110011001100110011001100110011001100110011001 |
封装函数doubleToBinaryString和binaryStringToDouble
/** * 数字转 IEEE 754 双精度二进制字符串 * @param {Number} num 任意数字 */ function doubleToBinaryString(num) { const buffer = new ArrayBuffer(8); // 64 位 = 8 字节 const view = new DataView(buffer); // 把数字写入 buffer(使用大端模式) view.setFloat64(0, num, false); // false 表示使用 big-endian let binaryStr = ""; for (let i = 0; i < 8; i++) { const byte = view.getUint8(i); binaryStr += byte.toString(2).padStart(8, '0'); } return binaryStr; }
/** * IEEE 754 双精度二进制字符串转数字 * @param {String} binaryString 任意表示双精度二进制的字符串 */ function binaryStringToDouble(binaryString) { const bstr = binaryString.replaceAll(/[^\d]/g, '') // 仅保留数字 if (bstr.length !== 64) { throw new Error("输入必须是 64 位二进制字符串"); } // 将每 8 位转成字节,填入 Uint8Array const bytes = new Uint8Array(8); for (let i = 0; i < 8; i++) { const byteStr = bstr.slice(i * 8, (i + 1) * 8); bytes[i] = parseInt(byteStr, 2); } // 用 DataView 读取 float64(双精度)数字 const view = new DataView(bytes.buffer); return view.getFloat64(0, false); // false 表示 big-endian(高位在前) }
浮点数的基础运算原理
此处,以0.1 + 0.2为例,介绍一下双精度数值两者+运算的运算过程:
首先,使用封装好的doubleToBinaryString函数,得到两者的IEEE 754的结构:
| Number | S | E | M |
|---|---|---|---|
| 0.1 | 0 | 011 1111 1011 | 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 |
| 0.2 | 0 | 011 1111 1100 | 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 |
然后,对齐指数(E):
0.1的指数位为011 1111 1011,,故,指数为
为博客添加MIDI播放功能
发布
在此页面上传和播放本地的MIDI文件。
最近花时间研究了MIDI这种文件格式,感觉发现了“新大陆”:和常见的音频文件(如.mp3/.flac等)不同,midi文件主要是记录事件(如各个音符的按压事件和控制器相关指令)而不是压缩后的音频数据,这也是为什么midi文件体积可以很小(数十KB)但是歌曲的播放时长却可以达到普通几MB的三、四分钟MP3文件的水平。MIDI文件最终播放时呈现的效果和加载的音色库相关,使用不同细腻度的音色库来播放MIDI文件,最终得到的歌曲听感会有差异。
目前主流的操作系统都可以直接播放MIDI文件,如果要在浏览器内播放MIDI,需要依赖Web Audio API,解析MIDI文件并加载音色库,合成音频再播放。
播放器设计
博客目前已经支持发布MIDI音乐的文章了,文章的路径ID如果是[m]开头的,即表示是带音乐的发文。
播放器的核心实现逻辑和代码参考了开源的在线MIDI编辑器signal-app:https://signal.vercel.app/edit,GitHub上仓库地址:https://github.com/ryohey/signal,这个编辑器实现基于TypeScript + React + Canvas。
博客的MIDI播放器在signal-app原来代码的基础上,精简掉大部分关于MIDI编辑的内容,把它作为一个纯粹的音频文件播放器来进行开发,最终的打包gzip体积仅增加了几十KB。目前播放器支持如下功能(后续播放器的功能会有所调整):
- 自适应布局,跟随博客切换主题
- 后退5秒
- 单曲循环
- 跳转开头
- 播放/继续播放/暂停/停止
- 静音/取消静音
- 节拍器功能
- 滑动进度条定位
- Track 静音
- 键盘模式(88/76/61/49键)切换
- 音符瀑布流可视化
- 固定播放自定义选择的小节
- 其他功能敬请探索...

博客播放器新的功能在开发中,当然也有一些BUG等待被发现和解决。以及,播放器在移动端上的性能问题,播放时会有一点卡顿,不过在我的开发机器Chrome上测试播放还是很流畅。
播放器初次加载时,因为要下载音色库文件,会比较缓慢,下载完毕后会将前置资源放至indexedDB进行缓存,之后就无需再次等待下载音色库了,点击即可直接播放MIDI。
最后
在博客内第一首发布的MIDI音乐是由德国乐队Fool's Garden带来的Lemon Tree,欢迎聆听。MIDI文件下载自:https://bitmidi.com/lemon-tree-mid
为什么把它作为博客第一首要分享的音乐?一是因为博客是基于Deno Fresh来开发搭建的,而这个Web框架的Logo就是一个柠檬!我觉得十分有意义;而每每听到这首熟悉的旋律,总能让自己回忆起高一开学前夏令营的时光:高中的英语老师在第一堂课上给我们放的就是这首英文歌。










