-
Notifications
You must be signed in to change notification settings - Fork 0
数学库的原理‐中文
此文档适用于数学库v.1.17(对应MC版本1.21.3),可能具有一定的时效性。
此页面会有五张图片。
其他的三角函数都能用三角公式由cos和sin变换而来。
所有反三角函数都是基于atan2的。
于是有
见:https://www.zhihu.com/question/31398449/answer/2449234079
- 当条目较少时,可以用四叉树进行穷举。
在return命令更新导致的函数机制更改后,函数的调用消耗增加了,因此在MC里最好的是四叉树。
- 当条目很多时,可以用函数宏查表。
首先单开一条函数用来导入数据库,把数据库的格式写得规整一点,例如:
##ln(x)的初始数据库
scoreboard players set #const_ln(1) ln_const 0
scoreboard players set #const_ln(2) ln_const 6931
scoreboard players set #const_ln(3) ln_const 10986
scoreboard players set #const_ln(4) ln_const 13863
scoreboard players set #const_ln(5) ln_const 16094
scoreboard players set #const_ln(6) ln_const 17918
scoreboard players set #const_ln(7) ln_const 19459
scoreboard players set #const_ln(8) ln_const 20794
scoreboard players set #const_ln(9) ln_const 21972
scoreboard players set #const_ln(10) ln_const 23026
……
用函数宏查这个库的方法就是:
execute store result storage large_number:math temp int 1 run scoreboard players get input int
execute store result score #ln(x).output int run function large_number:ln/1..10000 with storage large_number:math
# large_number:ln/1..10000
$return run scoreboard players get #const_ln($(temp)) ln_const
基本原理就是:"execute store result xxx + 倍率 + 命令返回值"
由于命令返回值只能是int,而倍率无论输入何值都会当做double处理,所以 "execute store result xxx + 倍率 + 命令返回值" 的本质就是int乘double,用函数宏传入倍率就是简单的乘法操作了。
注意:如若某个命令参数的值是5.4f这类,你get后在屏幕里显示的也是5.4f,但那只是显示给你看,实际上的命令返回值会进行向下取整并把范围限制在int范围内。大于2147483647的则一律返回2147483647。所以实际上get一个值为5.4f的浮点数的实际返回值是5。
相关概念:万进制数组、分段存储、前导零。
int能存储的数字相当有限,int的上限是2147483647。有时候我们会产生一个大于这个上限的数,我们管这种无法用单个int存储的数叫大数 (large number)。
如果我想存储一个大于2147483647的值,那么一个int显然是不够用的。我们可以把他按照一定的进制拆分成多个数组成的数组来存储。这就叫分段存储。
万进制数组的定义:int的范围内能不溢出且可以进行任意位相乘的十进制数位最大是四位。当值超过10000万时则再加一个数存储大于等于1万的部分,如果大于一万的部分的值超过一万万了,则再加一个数存储大于一亿的部分,以此类推。这就叫万进制int数组 (16-bit BCD array)。
示例:
1234 -> [1234]
12345 -> [1,2345]
100220345 -> [1,22,345]
观察上面示例的第三行,你会发现在进行数位拆分后,[1,0022,0345]实际上存储的是[1,22,345],如果直接用函数宏会进行拼接,会得到122345。那么,我该怎么读出这些不显示的0呢?
在万进制数组里,有些数字本应是四位,却因这些数字的开头是0导致数位不足四位。这些数字在读出的需要补齐前导零 (leading zeros)。
有一个技巧可以让你用函数宏把这些数拼起来时补齐缺失的前导零:
#把列表里的各元素取出来
data modify storage large_number:math stemp1 set value [1,22,345]
execute store result storage large_number:math temp1 int 1 run data get storage large_number:math stemp1[0]
execute store result score #temp2 int run data get storage large_number:math stemp1[1]
execute store result score #temp3 int run data get storage large_number:math stemp1[2]
#对于万进制数组的第一个以外的元素。例如这个列表第二项是22,把他加上10000变为10022,这时候它前面本应该有的0就显示出来了
execute store result storage large_number:math temp2 int 1 run scoreboard players add #temp2 int 10000
execute store result storage large_number:math temp3 int 1 run scoreboard players add #temp3 int 10000
#把前面的那个没必要的1截掉。此时10022变为 "0022"
data modify storage large_number:math temp2 set string storage large_number:math temp2 1
data modify storage large_number:math temp3 set string storage large_number:math temp3 1
function large_number:test2 with storage large_number:math
# large_number:test2
$data modify storage large_number:math stemp2 set value "$(temp1)$(temp2)$(temp3)"
最后你get一下“storage large_number:math stemp2”,便可得到"100220345",这是万进制数组里真正存储的数字。
大数加法
大数减法
进行大数减法前要先比较大数的大小,确认被减数不能小于减数。
大数乘法
- 记分板格式,精度为8位有效数字
浮点数的指数范围也就是-324~308,直接四叉树穷举。
什么叫浮点数的记分板格式?
示例:
符号:#float_sign int 1
尾数(也叫底数):#float_int0 int 44553375
指数:#float_exp int 23
则表示的数为: 1*0.44553375*10^23
- 数组格式,精度为16位有效数字
用data string法把底数和指数拆开,把底数四位一组拆成16位万进制int数组。
对于每一个数字,必定存在符号和数值。对于MC里的浮点数,指数、小数点位置和前导0数量这三个信息并不会同时变动,若其中一个变了,其他两个参数一定是固定值。也就是说,对于转化后的数字信息:
-
如果指数不为0,则小数点位置必定为2(在第一个数后面),前导0必定是0个。
-
SNBT的浮点数整数部分达到8位或小数的前导0数量多于3个就会以科学记数法形式显示。
-
如果小数点位置不为2,则指数必定为0,前导0必定是0个。
-
如果前导0数量为1到3个(MC浮点数最多存在三个前导0),则小数点位置必定为2,指数必定为0。
此外,SNBT的浮点数也可以以科学记数法的形式输入,比如 1.2E3d 表示1.2*10^3。以科学记数法形式输入时必须带数据单位。
double上下限的精确值是±1.797693134862315807E308
double的绝对值最小值是4.9E-324
loot spawn生成的掉落物实体可以无视tp的坐标上下限,故此算法可以计算全浮点数的加减法。
- 利用宏
就是前面讲的"用execute命令+函数宏进行简单的乘法"。
- 记分板方法
把浮点数转化为记分板格式进行大数乘法后用宏拼起来。
- 高精度浮点乘法
实际上就是记分板方法的精细化。把浮点数转化为数组格式后进行大数乘法。
时至2024.11.16,大数的除法仍然是大数运算里的最后一块拼图,还没有人能够解决。
大数和浮点数的除法技巧有些是通用的,因此放在一起讲。
注意,以下这些方法均不能处理除数为0的情况。
- 竖式法
没什么好讲的,自己列个竖式算一下就知道了。代码实现可见:https://github.com/kaer-3058/large_number/blob/main/data/large_number/function/division/list_div_const.mcfunction
- 连减法
除法的本质就是多个减法。
首先把被除数和除数的数位对齐。然后判断被除数和除数的大小。
如果被除数小于除数,则商0,并把被除数*10。
如果被除数大于等于除数,则被除数不断减去除数直到它小于除数,并把减的次数作为商。
- 展示实体法
展示实体的旋转矩阵在分解前会先把所有值除以最后一个数。
注:由于矩阵SVD,若输入值都是正数则输出的是必为正数,若输入值是负数则输出值不一定为正或负
输入 列表里第1、6、11个是被除数,最后一个数是除数
entity 28529-0-3d00-0-2c4200ee8401 transformation [1.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f,0.0f,0.0f,0.0f,1.0f]
输出
entity 28529-0-3d00-0-2c4200ee8401 transformation.scale
把万进制数组用宏拼一起作为一整个浮点数据然后代入展示实体的矩阵除法可实现大数除法。
- 小豆的十进制浮点除法
目前所有的浮点除法都是以此为核心实现的。
这是小豆用她的数论技巧来写的,但是她本人已经忘了当初怎么写的了,我也没研究明白,详见:https://github.com/xiaodou8593/math2.0/blob/main/data/math/functions/hpo/float/_div.mcfunction
利用分段除法,(a+b+c)/m = a/m+b/m+c/m,可实现数组除以八位整数。
目前无迭代大数除法理论上可以实现除数为任意多位,但除数的输入还是个难题,如能实现超过八位的除数输入即可解决大数除法。
就是初值估计+牛顿迭代法。
直接照抄:https://github.com/Triton365/fast_integer_sqrt/blob/main/functions/isqrt_nofunction.mcfunction
其实就是算向量的模长。
要计算向量A的模长,则先求A的单位向量B。向量A的模长就是向量A的曼哈顿范数除以向量B的曼哈顿范数。
原作者在README里有写:https://github.com/SuperSwordTW/Distance-Trig-Calc-3d
@e[sort=random],这是最快的。
核心就是逆波兰式 (也叫后缀表达式):https://blog.csdn.net/zm_miner/article/details/115324206