前往
大廳
主題

【指令】從MC學數學系列② ─ 利用scoreboard和attributes實現小數運算

雪色 | 2022-05-04 21:14:52 | 巴幣 2124 | 人氣 205

這裡又是稍久不見的雪色
由於此系列的文章有點難產,而我最初原本的打算是在整份Datapack或是系統架構成型時在發布,
也就是現階段還算是在開發中,內部的寫法會一直改變,
再加上上學期作業量變多,
所以就有點小拖稿了owo.
(也終於是讓我等到暑假了,不過只有前兩個禮拜可以放假,之後又要開忙了QQ)

考量到我之後並不會非常常接觸Minecraft指令,所以這個系列預計會變成3~5個月發布一篇文章,
而目前我也將此系列的文章逐步整合到HackMD中,不過因為內容非常不全,所以暫時先不放上來獻醜

而此篇主要是輕量科普有什麼技巧可以在Minecraft原版環境中實現方便且快速的小數運算

( ⚠ 本篇文章難度很高,請至少熟悉以下的先備知識後會比較容易閱讀 )
Attribute(屬性)、scoreboard, data, execute store指令


在此篇文章能學到什麼 ?

一、分析小數、整數、指數之間的區別
二、熟悉並加以應用Attribute
三、使用數學方法計算小數除法
四、熟悉數學與Minecraft指令之間的關係



目錄

一、小數運算(Fractional Arithmetic Operation)
二、如何用scoreboard實現小數運算
三、如何用attribute實現小數運算
四、如何用減乘法實現除法運算(Division Operation)
五、次篇預告



一、小數計算(Fractional Arithmetic Operation)

Minecraft中處理小數資料的運算很不方便,故我們將此獨立成一篇文章來細談,
與一般記分板有很大的差別在於,記分板所使用的型別是整數(Integer)
但大部分實體或物品Data中的資料型態,例如Pos、Motion等都並非使用integer型態,
而是使用像是浮點數(float)雙精度浮點數(double)的型態,
這很容易使得用一般記分板換算浮點數時容易出錯,或是耗費大量時間去計算出結果。
而目前國內國外版也沒有一個比較系統化的文章,
所以在此將小數計算的基礎架構獨立成一篇文章來論述。



二、如何用scoreboard實現小數運算

這個方法應該是最容易想到的方法,其作法也很直接明瞭。
對於每筆數值,都需要將其儲存成兩份資料,儲存資料的方式可以開三個記分板:
1. int
:整數部分(Integer Part)
2. frac小數部分(Fractional Part)
3. exp指數部分(Exponent Part)

例如 15.75,我們會將其存成數組 (int, frac, exp)=(15, 75, -2)
但若 exp 的值比-9還小時,即有可能會出現溢位問題,
如果要處理此類型的計算則需要用到其他的方式來處理


2.1簡單定義一下scoreboard的小數運算



  • 加法運算



  • 減法運算



  • 乘法運算


三、如何用attribute實現小數運算

相對於一般的scoreboard,可以跳過實作加減乘法的過程,
取而代之的是使用Minecraft的Attributes的功能 (泛指 屬性(Attribute),不一定是指attribute指令)

3.1屬性修飾符(Attribute Modifiers)

一共有4個參數
Name 修飾符的名稱,不是特別重要
Amount 修飾符的值
UUID[4] 帶四個整數(int)的陣列,在同一個修飾符組中不得有重複者
Operation Amount進行指定的屬性操作方式,一共有3種操作選項:
0: add, 1: multiply_base, 2: multiply


其結算方式可以直接參考此式:

:所有修飾符中Operation為add者的Amount總和
:所有修飾符中Operation為multiply_base者的Amount總和
: 所有修飾符中Operation為multiply者,將各者Amount加1之後再乘起來的總值

如果對於Attribute不是很熟悉的人,可以參考:屬性 - Minecraft Wiki



3.2好像有一些問題

首先我們實作上會遇到兩個問題:
1. 計算乘積時會需要處理式子中額外+1的問題
2. 沒有辦法「修改」已存在的修飾符之Amount值

針對第一點其實並不難,我們可以將其變成一個「二元運算」的方式就好,計算時再額外放入一個{Operation:1, Amount:-1}的東東來去除+1的部分

第二點的問題如果想測試的可以自行測試看看:
data modify entity ... Attributes [0].Modifiers[0].Amount set value 77.0

執行完後查詢同樣路徑,會發現值沒有更改
data get entity ... Attributes [0].Modifiers[0].Amount

比較難處理的是第二點,如果改不了屬性值不就等於沒救了?
不等等,還有一個替代方案可以處理這個問題



3.3、解法

這條指令是可以成功修改修飾符的:
data modify entity ... Attributes[0].Modifiers set value {Name:"add", Amount:1.0d, Operation:0, UUID:[I;0,0,0,0]}

與其說是修改,倒不如說是新放入一個新的修飾符,因為舊的Modifiers資料都不會消失
只有set和merge的操作是有效的,其他像是append, prepend, insert全部都沒效

故我們要做的是就是先建構好Modifiers的部分,
可以利用storage開一個陣列來建,過程並不會特別複雜



3.4利用實體的Attributes實作加減乘法

到這裡理論上就可以使用Attribute的方式來實現出簡單的小數運算了,
我們可以先讓計算的媒介先用實體的方式來計算 (使用實體來實作的方式是可選的(?
基本上「加減乘法」皆適用這樣的流程方式來計算:
1. 建構Modifiers列表
2. 生成實體,初始化屬性Base值為0,並將建好的Modifiers放到該屬性中
  • 加法運算(Addition Operation)
  • 減法運算(Substraction Operation)
  • 乘法運算(Multiplication Operation)

建議可以嘗試推導出連加 (a+b+c+d) 與 連乘 (a×b×c×d) 的方式



3.5、取得計算結果

我個人是建議一次將所有修飾符都建立完成後,再合併到實體上
接下來就可以藉由attribute指令來取出計算結果

目前我個人使用的是movement_speed,他可以表示0.0 ~ 1024.0的數值
(屬性的值域同樣可以參考 屬性 - Minecraft Wiki)
而且仔細看會發現,Minecraft的屬性一律都沒有辦法表示負數 (除了玩家身上的一個屬性),
所以我們還會需要額外使用幾條指令來處理負數處理的部分
(我這裡是使用座標去判斷,當然也可以把它塞到記分板裡
(可以判斷:如果取出來的值是負的,那就額外塞入一個-1的multiply修飾符來讓他變成負的

  1. data modify entity @s Attributes[{Name:"minecraft:generic.movement_speed"}].Modifiers set from storage memory Modifiers
  2. data modify storage memory var set value 0.0
  3. execute store result entity @s Pos[1] double -0.000000476837158203125 run attribute @s minecraft:generic.movement_speed get -2097152
  4. execute unless predicate math:y_is_zero run data modify storage calculator var set from entity @s Pos[1]
  5. execute if predicate math:y_is_zero run data modify entity @s Attributes[{Name:"minecraft:generic.movement_speed"}].Modifiers set value [{Name:"negative", Amount:-2.0d, Operation:2, UUID:[I;2,0,0,0]}]
  6. execute if predicate math:y_is_zero store result storage calculator var double 0.000000476837158203125 run attribute @s minecraft:generic.movement_speed get -2097152

  1. 將Attribute Modifiers套於實體上
  2. 初始化storage memory的var (若無初始化在execute store的時候會找不到)
  3. 偏移31-10=21 bit,並套於當前實體的y軸座標上
  4. 若判斷y軸座標不為0時,則將y軸座標搬到storage memory的var上,即為計算結果
  5. 若判斷y軸座標為0時,將實體額外放入一個乘以-1的計算步驟
  6. 若判斷y軸座標為0時,重新用attribute get取得計算結果並偏移21 bit放置於storage memory的var上



四、如何減法與乘法實現除法運算(Division Operation)

我們這裡使用的方式是最容易理解的 牛頓法(Newton Method)
不過我們暫時先不講解牛頓法的原理與通式,因為會牽扯到高中期末才會上到的微積分
(我們會將此部分留到之後再詳細說明)

目前已知k值,試求1/k的值為何?
計算除法會使用這個遞迴式:

在最一開始是不知道的,我自己很推薦是用記分板大略估算出一個數值給他
(詳細推估的公式會很麻煩,我自己並不推薦用那套公式)

假設目前k值為21,
我們可以設定,用Minecraft指令來看的話會像是這樣:
(註:⌊k⌋是「下高斯符號」,又稱作floor(地板),作用是把數值往下取至整數,例如⌊17.9⌋=17)
data merge storage memory {k: 100.0}
execute store result score #y var run data get storage memory k 100
scoreboard players operation #x var = 10000 const
scoreboard players operation #x var /= #y const
execute store result storage memory x0 double 0.01 run scoreboard players get #x var

設定storage的memory中的k值為100.0d
設定記分板上的 #y[var] = k×100
設定記分板上的 #x[var] = 10000
#x[var] 除去 #y[var] 的值,會得到 ⌊10000/(k×100)⌋
將記分板上的 #x 儲存回storage的memory的x0中,並設定轉換倍率為0.01


接下來我們可以嘗試遞迴3次
其值與已經相當接近
中間過程的乘法與減法即是套用上述計算小數點加減乘法的方式來實現

詳細在Wiki都有非常詳細的講解,並且也有關於各種除法運算的版本,
如果有興趣的人可以查閱:Division algorithm

4.1 還是有一些問題

因為execute指令設計不當,Minecraft處理資料搬移時非常容易出現精準度丟失的情形,
execute有提供倍率轉換功能,但接收的數值不知道為什麼都是用integer來接收,
這導致每一次使用execute都要需要考量精準度會丟失多少的問題,說真的這非常的惱人

有關於這部分,我們在下個文章中會提供2種處理精度問題解法



五、次篇預告

此篇主要的功能是將Minecraft指令引入數學的概念,可以了解如何把數學概念與Minecraft指令做結合,
也是作為銜接後續文章的前導文之一

而在下一篇,我們會詳細描述要如何使小數精準度有效修正的方法,
會提及電腦的背景運作方式,像是二進制、小數點的儲存方式等,內容可能會很難owo...
如果篇幅足夠,我還會稍微提及其他計算機上的數學應用方法,不過感覺光是處理精度就夠講一大篇了

在最近不久我還會發一篇有關於指令效能分析相關的文章,可以期待一下owo (很數學,請慎入(X





創作回應

追蹤 創作集

作者相關創作

更多創作