昨天咱們介紹 IDD Features 時,有提到其 Insulin Concentration 欄位是以 SFLOAT 來儲存,它是什麼呢?不是已經有 IEEE 754 這個浮點數標準了,為什麼又要提出一個新的格式?
其實設計 SFLOA 的目的是為了:
將「具足夠精確度」的數值以「高效」又「節能」的方式傳輸。
最常使用的 IEEE 754 浮點數格式是 32 位元和 64 位元的版本,但這對於低功耗藍牙應用來說,負擔過重,所以藍牙協會在 Bluetooth 4.0 時,提出 SFLOAT 做為浮點數格式:
指數 | 尾數 |
---|---|
b15 - b12 | b11 - b0 |
高位元 | 低位元 |
指數和尾數都是以 2 的補數來表示,換算方式為:
指數:-8*b15 + 4*b14 + 2*b13 + b12
尾數:-2048*b11 + 1024*b10 + 512*b9 + ... + 4*b2 + 2*b1 + b0
指數範圍:-8 (0b_1000) ~ 7 (0b_0111)
尾數範圍:-2048 (0b_1000_0000_0000) ~ 2047 (0b_0111_1111_1111)
範例 1. 0b_0010_0000_0000_1101
0b_0010 = 2
0b_0000_0000_1101 = 13
13 * 10^2 = 1300
範例 2. 0b_1110_1000_0000_1101
0b_1110 = -2
0b_1000_0000_1101 = -2035
-2035 * 10^-2 = -20.35
但要注意有幾個特殊值不能轉為數值:
數值 | 意義 |
---|---|
0x07FF | Not a Number |
0x0800 | Not at this resolution |
0x07FE | Positive infinity |
0x0802 | Negative infinity |
0x0801 | Reserved for future use |
將 SFLOAT 轉為浮點數很簡單,只要照著 2 的補數的定義轉換即可:
def sfloat_to_float(value: int) -> float:
mantissa = value & 0x0FFF
mantissa -= (mantissa & 0x0800) << 1
exponent = (value >> 12) & 0x0F
exponent -= (exponent & 0x08) << 1
return mantissa * 10**exponent
將浮點數轉換為 SFLOAT 就比較麻煩一點了,比如 12 可以有如下表示:
值 | SFLOAT |
---|---|
12 * 10^0 |
0b_0000_0000_0000_1100 |
120 * 10^-1 |
0b_1111_0000_0111_1000 |
1200 * 10^-2 |
0b_1110_0100_1011_0000 |
在此例中,無論哪個都是可行的,但若是 1.2345 呢:
值 | SFLOAT |
---|---|
1 * 10^0 |
0b_0000_0000_0000_0001 |
12 * 10^-1 |
0b_1111_0000_0000_1100 |
123 * 10^-2 |
0b_1110_0000_0111_1011 |
1234 * 10^-3 |
0b_1101_0100_1101_0010 |
12345 * 10^-4 |
0x0800 (Not at this resolution) |
可以看到,SFLOAT 無法完整儲存 1.2345,因為 12345 已超過尾數可儲存的最大值。那麼退而求其次,儲存 1.234 可能是最好的選擇,但這因需求而異,例如精度要求只有小數點後一位,那結果又不同了。
本喵的設計規則如下:
1.23
會儲存成 123 * 10^-2
1.23
不會儲存成 1230 * 10^-3
123456.78
會以 1234.5678 * 10^2
為基礎1234.5678 * 10^2
會儲存成 1235 * 10^2
1000
儲存為 1 * 10^3
def float_to_sfloat(value: float) -> int:
if value == 0:
return 0
if value < 0:
value = -value
is_negative = True
mantissa_max = 0x0800
else:
is_negative = False
mantissa_max = 0x07FF
exponent = 0
# 放大數值,好儘量保存小數部分
while math.floor(value) != value:
value_scale_up = value * 10
if value_scale_up <= mantissa_max and exponent > -8:
value = value_scale_up
exponent -= 1
else:
break
# 縮小數值,好讓數值不超出規範
# 比如 123456.78 轉為 1234.5678 * 10^2
while value > mantissa_max and exponent < 7:
value /= 10
exponent += 1
# 四捨五入整數部分
# 比如 value 為 123456.78,
# 經由前一步驟,value 變為 1234.5678
# 四捨五入後,value 為 1235
# 原數值由 123456.78 變為 123500
mantissa = math.floor(value + 0.5)
# 處理 SFloat 的特殊值
if exponent == 0 and mantissa >= 0x07FE:
exponent = 1
# mantissa 除以十後四捨五入
mantissa = (mantissa + 5) // 10
# 縮小數值,以儲存最少的 mantissa
# 比如 1000,儲存為 1 * 10^3
value_scale_down = mantissa
while True:
value_scale_down //= 10
value_round = value_scale_down * 10
if value_round == mantissa and exponent < 7:
mantissa = value_scale_down
exponent += 1
else:
break
if mantissa > mantissa_max:
return NRES
elif is_negative:
mantissa = -mantissa
return ((exponent & 0x0F) << 12) | (mantissa & 0x0FFF)
連 SFLOAT 都寫那麼久 ... ∑( ̄□ ̄;)