iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Software Development

以MicroPython在ESP32上實作Insulin Delivery Service系列 第 10

Day 10 - 藍牙低功耗環境下的浮點數 SFLOAT

  • 分享至 

  • xImage
  •  

昨天咱們介紹 IDD Features 時,有提到其 Insulin Concentration 欄位是以 SFLOAT 來儲存,它是什麼呢?不是已經有 IEEE 754 這個浮點數標準了,為什麼又要提出一個新的格式?

1. 概述

其實設計 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

2. SFLOAT 轉浮點數

將 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

3. 浮點數轉 SFLOAT

將浮點數轉換為 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. 放大數值,好儘量保存小數部分。比如 1.23 會儲存成 123 * 10^-2
  2. 若已沒有小數部分,就停止放大。比如 1.23 不會儲存成 1230 * 10^-3
  3. 縮小數值,好讓數值不超出規範。比如 123456.78 會以 1234.5678 * 10^2 為基礎
  4. 尾數四捨五入到整數。比如 1234.5678 * 10^2 會儲存成 1235 * 10^2
  5. 縮小數值,以儲存最少的尾數。比如 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 都寫那麼久 ... ∑( ̄□ ̄;)


上一篇
Day 09 - 實作最簡單的 Characteristic - IDD Features
下一篇
Day 11 - E2E-Protection
系列文
以MicroPython在ESP32上實作Insulin Delivery Service31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言