概述
在串口通信、蓝牙通信中,经常需要将浮点数转换为字节发送,接收后再还原。App Inventor没有内置浮点数-字节转换,需要手动实现。
已有文档:hex_binary.md 介绍了进制转换和位运算。本文聚焦浮点数与字节的转换。
IEEE 754浮点数格式
单精度(32位/4字节)
| 部分 | 位数 | 说明 |
|---|---|---|
| 符号位 | 1位 | 0=正,1=负 |
| 指数位 | 8位 | 偏移127 |
| 尾数位 | 23位 | 小数部分 |
转换示例
浮点数 3.14 的IEEE 754表示:
二进制: 0 10000000 10010001111010111000011
十六进制: 40 48 F5 C3
字节序(大端): [0x40, 0x48, 0xF5, 0xC3]
方案一:通过WebView用JavaScript转换(推荐)
JavaScript的 DataView 提供精确的浮点数-字节转换:
HTML页面
<!-- float_converter.html -->
<script>
setInterval(function() {
var msg = window.AppInventor.getWebViewString();
if (msg && msg !== "") {
try {
var data = JSON.parse(msg);
if (data.cmd === "float2bytes") {
var buf = new ArrayBuffer(4);
var view = new DataView(buf);
view.setFloat32(0, data.value, data.littleEndian || false);
var bytes = [];
for (var i = 0; i < 4; i++) bytes.push(view.getUint8(i));
window.AppInventor.setWebViewString(JSON.stringify({result: bytes}));
} else if (data.cmd === "bytes2float") {
var buf = new ArrayBuffer(4);
var view = new DataView(buf);
for (var i = 0; i < data.bytes.length; i++) view.setUint8(i, data.bytes[i]);
var value = view.getFloat32(0, data.littleEndian || false);
window.AppInventor.setWebViewString(JSON.stringify({result: value}));
} else if (data.cmd === "double2bytes") {
var buf = new ArrayBuffer(8);
var view = new DataView(buf);
view.setFloat64(0, data.value, data.littleEndian || false);
var bytes = [];
for (var i = 0; i < 8; i++) bytes.push(view.getUint8(i));
window.AppInventor.setWebViewString(JSON.stringify({result: bytes}));
}
} catch(e) {
window.AppInventor.setWebViewString(JSON.stringify({error: e.message}));
}
}
}, 50);
</script>
App Inventor积木块
when Screen1.Initialize() {
WebViewer1.HomeUrl = "file:///android_asset/float_converter.html"
}
// 浮点数转字节
procedure 浮点数转字节(数值, 小端序) {
global JSON = join("{\"cmd\":\"float2bytes\",\"value\":", 数值, ",\"littleEndian\":", 小端序, "}")
WebViewer1.WebViewString = JSON
// 等待WebView回调
}
when WebViewer1.WebViewStringChange(value) {
global 结果 = JsonTextDecode(value)
if (Dictionary.Get(结果, "result") != "") {
global 字节列表 = Dictionary.Get(结果, "result")
标签_字节.Text = join(字节列表)
}
}
// 字节转浮点数
procedure 字节转浮点数(字节列表, 小端序) {
global JSON = join("{\"cmd\":\"bytes2float\",\"bytes\":[", join(字节列表), "],\"littleEndian\":", 小端序, "}")
WebViewer1.WebViewString = JSON
}
方案二:纯积木块实现(近似)
对于精度要求不高的场景,可以用数学方法拆分:
procedure 浮点数转近似字节(数值) {
// 简化版:将浮点数拆为整数部分和小数部分
// 注意:这不是标准IEEE 754,是自定义简化协议
global 整数部分 = math.floor(math.abs(数值))
global 小数部分 = 数值 - 整数部分
// 整数部分用2字节
global 高字节 = math.floor(整数部分 / 256)
global 低字节 = modulo(整数部分, 256)
// 小数部分用2字节(乘以10000保留4位精度)
global 小数整数 = math.floor(小数部分 * 10000)
global 小数高字节 = math.floor(小数整数 / 256)
global 小数低字节 = modulo(小数整数, 256)
// 符号字节
global 符号 = 0
if (数值 < 0) {
global 符号 = 1
}
global 转换结果 = [符号, 高字节, 低字节, 小数高字节, 小数低字节]
}
procedure 近似字节转浮点数(字节列表) {
global 符号 = selectListItem(字节列表, 1)
global 整数部分 = selectListItem(字节列表, 2) * 256 + selectListItem(字节列表, 3)
global 小数部分 = (selectListItem(字节列表, 4) * 256 + selectListItem(字节列表, 5)) / 10000
global 结果 = 整数部分 + 小数部分
if (符号 == 1) {
global 结果 = -结果
}
global 解析结果 = 结果
}
方案三:通过蓝牙/串口直接发送
大多数嵌入式系统使用简单的自定义协议而非IEEE 754:
发送温度值(2字节整数 × 10)
procedure 发送温度(温度值) {
// 温度值 × 10 保留1位小数,用2字节整数发送
global 值 = math.floor(温度值 * 10)
global 高字节 = math.floor(值 / 256)
global 低字节 = modulo(值, 256)
// AI2没有直接字符函数,这里假设有扩展或者自定义过程,仅作演示
global 发送数据 = join(高字节, 低字节)
BluetoothClient1.SendText(发送数据)
}
// 接收端还原
procedure 还原温度(高字节, 低字节) {
global 值 = 高字节 * 256 + 低字节
global 温度 = 值 / 10.0
}
字节序(大小端)
| 字节序 | 说明 | 常见场景 |
|---|---|---|
| 大端序(Big-Endian) | 高位字节在前 | 网络协议、Java |
| 小端序(Little-Endian) | 低位字节在前 | x86、ARM、大多数MCU |
' 大端序: 3.14 → [0x40, 0x48, 0xF5, 0xC3]
' 小端序: 3.14 → [0xC3, 0xF5, 0x48, 0x40]
常见问题
Q1: 转换后数值有误差?
IEEE 754单精度只有约7位有效数字。3.14 可能变成 3.140000104904175。这是正常的浮点数精度问题。
Q2: 发送后MCU解析错误?
- 确认两端使用相同的字节序
- 确认数据类型一致(float/double)
- 使用hexdump工具查看原始字节确认
Q3: 有没有现成扩展?
部分串口扩展内置了浮点数转换功能。如果使用纯BluetoothClient,需要自行实现。
总结
| 方案 | 精度 | 难度 | 推荐度 |
|---|---|---|---|
| WebView + JS | ✅ IEEE 754 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 简化协议 | 近似 | ⭐ | ⭐⭐⭐ |
| 整数缩放 | 完全精确 | ⭐ | ⭐⭐⭐⭐ |
扫码添加客服咨询