超长数字计算丢失精度问题的详细解决过程

wils

创作者俱乐部成员

首先,详细描述一下浮点数造成的问题:

  • 如图,VBA、JSA、Python、Powershell里,都认为0.1 + 0.2不等于0.3,是不是非常恶心,常见语言里用浮点计算必须十分小心

  • 在wps的公式里计算超过15位的数字时,会变成科学计数法,如果改变单元格格式,可以发现数字末尾全变成了0,数字丢失了精度

  • 还有就是前几天吐槽过的round公式的奇怪行为


然后,常见的解决方法就是把数字当成字符串来处理,比如,自己写个加法,按照十进制逐个字符相加,进位,最后生成相加的字符串,这样很好,但需要写很多代码。

所以我们去找现成的库,比如,npm里的js库big.js既很小,又可以在JS宏里直接使用;又比如busybox-w32里的bc命令,无限精度计算,没有浮点误差。两者都是很好的选择


如何使用big.js

  • 去github上下载big.js(不能发链接),用记事本打开,全选复制内容

  • 打开wps宏编辑器,点击工具,选项,编译,起码要关掉图中第一项,"禁止全局表达式"

  • 然后把big.js粘贴到当前模块,再新建一个模块,根据big.js的说明书,写一个自定义公式

🔔

function sum7(x)

{

let n = new Big(x)

return n.plus(7).toFixed(0)

}

意思就是:从单元格传递过来的长数字字符串是x

用new Big(x)生成一个类似逐个字符组成的十进制数字对象n

然后这个n有plus、times等计算方法,可以调用这些方法获取计算结果

这个对象的计算可以有任意精度


如何使用bc命令

  • 去github下载busybox.exe,可以把这个exe放到C:\Windows目录里,也可以放到环境变量PATH里任何一个目录里,目的是在命令行任何地方都可以调用这个exe

  • 然后在当前xlsm文件的相同目录下建立cgi-bin文件夹,目的是在这个目录启动httpd服务,脚本都写在cgi-bin里,其实可以放到任何其他目录,只是后面需要相应修改

  • 在cgi-bin里新建文本文件bc.sh,内容如下,前两行声明这是Shell脚本,内容是utf8编码,第三行是构造一句加法,传递给bc计算,返回结果

📌

#!/bin/sh

echo -e 'Content-type: text/plain; charset=utf-8\n'

sed 's/.*/& + 7/' | bc -l

  • 最后在JS宏里定义宏,内如如下,第一句先尝试关掉已有的httpd,然后启动httpd服务,第二句fetch发送post请求,内容是A1单元格的内容,把接收到的计算结果打印到立即窗口

👋

unction test()

{

Shell(`cmd /c busybox pkill httpd || busybox httpd -p 8080 -h "${ThisWorkbook.Path}"`, jsHide)

fetch(`http://localhost:8080/cgi-bin/bc.sh`,{method: "POST", body: Range("A1").Value2})

.then(r=>r.text()).then(console.log)

}

😁还好,两种方式写的代码都不多

说点题外话,为什么要用命令:

总的来说,因为有读写外部文件的需求

VBA里可以通过com对象,访问外部文件、数据库、程序

JS里现有的访问外部的方法里:

  • open、put、lineinput命令,get目前没法用,lineinput有每行长度限制,最后一行都不进来,总之就是非常不稳定

  • nativex文档太少,c++太难,linux下说是可以编译成so文件,但没见到任何例子

  • xll加载项,不能用混合模式的库,卸载时报错,linux好像不能用

  • wpsjs加载项,OAAssist不能用了,但是可以通过上面提到的httpd,开启cgi服务,读写外部文件,支持linux

  • fetch,同样需要开启cgi服务,支持linux

所以,可以看出linux专业版支持的JS宏里可以用fetch,linux个人版可以用wpsjs加载项,win版里其他方法都不太稳定fetch最稳定,wpsjs方便分享,但总的来说,想访问外部数据,都需要cgi服务,而busybox-w32这个exe只要600K大小,其中的httpd命令提供了一个基本的cgi服务,busybox里剩下的100多个命令提供了大量读写外部数据的方法,如上面所示,脚本基本就是3行代码。

总的来说,用这个方式给JS宏提供对外操作数据的接口,可以享受fetch的成熟稳定,也只需要付出600K的一个文件,和3行的脚本即可

当然,如果你熟悉任何语言,都可以简单的开启cgi服务,这不是唯一也不是最合适的选择,只是很容易上手进行尝试

前后呼应一下,bc里认为0.1+0.2等于0.3,哈哈

广东省
浏览 2001
收藏
3
分享
3 +1
6
+1
全部评论 6
 
,但第2种方式 fetch().then(r=>r.text()).then(console.log) 返回值如何传在单元格公式里?
· 广东省
回复
wils

创作者俱乐部成员

fetch().then(r=>r.text()).then(t=>{Range("A2").Value2=t}) 应该可以,但需要整理一下t的格式
· 海南省
回复
 
打卡学习
· 陕西省
回复
 
· 首尔
回复