#swift #math #floating-point #byte #core-bluetooth
#swift #математика #с плавающей запятой #байт #ядро-bluetooth
Вопрос:
Я получаю данные акселерометра с устройства BLE в виде 6 байт, и я хочу преобразовать их в значение с плавающей запятой, чтобы быть как можно более точным.
Каждые 2 байта представляют разные оси (x, y и z соответственно). Вместо числа, подобного 982, я хочу число, подобное 0.98243873
Я попытался преобразовать байты в число с плавающей точкой, попробовав…
let data = characteristic.value!
let floatX = Float(bitPattern:UInt32(littleEndian:data[0...1].withUnsafeBytes{$0.pointee}))
OR
let floatX = data[0...1].withUnsafeBytes{$0.pointee} as Float
но я получаю странные числа, например, -6.777109e-21
когда значение Int16 равно 1047
, и я ожидаю чего-то вроде 1.04793283
. Будет ли это как-то связано с подписываемыми байтами? Могу ли я даже получить такую точность из двух байтов?
Комментарии:
1. Я бы не ожидал получить большую точность от 16-битного числа с плавающей точкой.
2. Каков точный макет данных? Байты расположены в порядке окончания малого или большого?
3. Двухбайтовые значения могут быть закодированы различными способами в зависимости от устройств. Пожалуйста, укажите, какое устройство вы используете, и таблицу данных о выводе акселерометра устройства.
4. Два байта, скорее всего, являются просто целым числом, представляющим ускорение в некоторых единицах (таких как квадратные метры в секунду, или миллиметры в секунду, или метры в секунду, умноженные на 256, или какое-либо другое масштабирование). В этом случае вы могли бы преобразовать измерение в число с плавающей запятой, просто умножив или разделив на любое желаемое масштабирование. Не было бы никакой необходимости манипулировать байтами, представляющими объект с плавающей запятой.
5. Почему вы ожидаете, что 1047 станет 1.04793203? Почему вы не хотите, чтобы 1047 стал 1.047, или настолько близким к 1.047, насколько может представлять тип с плавающей запятой?
Ответ №1:
Проблема в том, что вы пытаетесь превратить в UInt32
значения в Float
конце просто путем «переосмысления» тех же битовых шаблонов, что и новое значение (для этого Float(bitPattern:)
и предназначено), но это совсем не то, как Float хранит свои данные. Типы данных Swift Float
и Double
являются реализациями 32- и 64-разрядных типов данных с плавающей запятой из IEEE 754. Существует множество онлайн-ресурсов, которые объясняют это, но TL; DR заключается в том, что они хранят числа аналогично научной записи, с мантиссой, возведенной в степень экспоненты.
Я думаю, что часть вашей трудности связана с попыткой сделать слишком много сразу. Разбейте его на мелкие кусочки. Напишите функцию, которая принимает ваши данные и разлагает их на 3 UInt32
компонента. Затем напишите отдельную функцию, которая выполняет любое преобразование, которое вы хотите, для этих компонентов, например, превращает их в числа с плавающей точкой. Вот грубый пример:
import Foundation
func createTestData(x: UInt32, y: UInt32, z: UInt32) -> Data {
return [x, y, z]
.map { UInt32(littleEndian: $0) }
.withUnsafeBufferPointer { Data(buffer: $0) }
}
func decode(data: Data) -> (x: UInt32, y: UInt32, z: UInt32) {
let values = data.withUnsafeBytes { bufferPointer in
bufferPointer
.bindMemory(to: UInt32.self)
.map { rawBitPattern in
return UInt32(littleEndian: rawBitPattern)
}
}
assert(values.count == 3)
return (x: values[0], y: values[1], z: values[2])
}
func transform(ints: (x: UInt32, y: UInt32, z: UInt32))
-> (x: Float, y: Float, z: Float) {
let transform: (UInt32) -> Float = { Float($0) / 1000 } // define whatever transformation you need
return (transform(ints.x), transform(ints.y), transform(ints.z))
}
let testData = createTestData(x: 123, y: 456, z: 789)
print(testData) // => 12 bytes
let decodedVector = decode(data: testData)
print(decodedVector) // => (x: 123, y: 456, z: 789)
let intsToFloats = transform(ints: decodedVector)
print(intsToFloats) // => (x: 0.123, y: 0.456, z: 0.789)
Ответ №2:
Если вы получаете 6 байт, где каждые 2 байта представляют ось, вы фактически получаете 3 числа, 16-битные целые числа каждое. Чтобы преобразовать их в число с плавающей точкой, которое вы хотите (где 1047
—> 1.047xxx
), вы, очевидно, должны разделить каждое из них на 1000.0
.
Я не знаю Swift, но просто делаю что-то вроде этого псевдокода:
x = (first_byte 256 * second_byte) / 1000.0; // or vice versa, if big-endian
y = (third_byte 256 * fourth_byte) / 1000.0; // same...
z = (fifth_byte 256 * sixth_byte) / 1000.0; // same again
Как я уже указывал, если байты, выходящие из устройства, имеют порядковый номер, вы, очевидно, делаете:
x = (256 * first_byte second_byte) / 1000.0;
// etc...
Возможно, вы можете немного ускорить это, умножив на обратное, т. Е. * 0.001
, потому что умножение обычно происходит немного быстрее, чем деление. Некоторые компиляторы делают это за вас, хотя, если они замечают, что это константа:
x = (...) * 0.001;
Комментарии:
1. Спасибо! Кажется, это именно то, что я ищу. Попробую это в понедельник и обновлю вас!