Swift: как преобразовать байты в число с плавающей точкой / получить более точное число?

#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. Спасибо! Кажется, это именно то, что я ищу. Попробую это в понедельник и обновлю вас!