2016-11-04 4 views
4

Не вдаваясь в слишком много деталей, у меня есть две встроенные системы, ни одна из которых не может использовать библиотеку с плавающей запятой. Между ними - мобильное приложение, где выполняются некоторые вычисления. Один расчет должен поддерживать точность. Это значение отправляется клиенту через Bluetooth, как массив байтов. Когда он будет получен, я буду хранить его как uint32. Затем это значение снова используется совместно с мобильным приложением, где требуется точность. Там нет проблем, потому что я могу использовать класс ByteBuffer Java. Проблема в том, что мне также нужно поделиться этим значением с микропроцессором Z80 как с uint8, потому что он передается через UART и используется как счетчик ADC (0-255), поэтому он теряет точность (но в этом нет необходимости конец).Преобразование uint32 с плавающей запятой в uint8

Так что я делаю это в моем мобильном приложении с помощью Java:

int bits = Float.floatToIntBits(fAdcCount); 
byte[] b = new byte[4]; 
b[0] = (byte)(bits & 0xff); 
b[1] = (byte)((bits >> 8) & 0xff); 
b[2] = (byte)((bits >> 16) & 0xff); 
b[3] = (byte)((bits >> 24) & 0xff); 

b затем отправляется в BLE характерной записи в BLE микроконтроллера. Микроконтроллер BLE затем считывает этот буфер в виде 32-битного слова little-endian и сохраняет его в uint32. Глядя на это uint32 в отладчике, отображаются правильные значения, и, как я уже сказал выше, я могу вернуть этот uint32 в массив байтов и отправить его в мобильное приложение и прочитать его с использованием класса ByteBuffer Java. Это отлично работает, я получаю правильное значение с плавающей запятой.

Проблема в том, что мне нужна целая часть этого представления с плавающей запятой для отправки на микропроцессор Z80 через UART в качестве uint8, поскольку он используется как счетчик ADC от 0 до 255.

Итак, как я могу преобразовать значение с плавающей запятой, которое было завернуто в uint32 (порядок младших байтов), чтобы uint8 потерял точность? Я также знаю, что диапазон составляет от 0 до 255, то есть никогда не будет больше 255,0 для преобразования.

Например, если у меня есть это:

uint32 fValue = 0x43246ADD; // = 164.417...

Как я могу получить это без использования поплавка ?:

uint8 result = 164;

+4

Вы должны знать конкретный формат с плавающей точкой кодирования, которая не является обязательной в C. Вот [один такой формат] (https://en.wikipedia.org/wiki/Single-precision_floating-point_format). И интегральная часть может, скорее всего, не вместить 8 бит в любом случае. –

+1

Можете ли вы объяснить, почему у вас есть значение с плавающей запятой, хранящееся в 'uint32'? И почему вы используете 'uint32', а не стандартный' uint32_t', определенный в ''? –

+1

@KeithThompson Конечно, я работаю с двумя встроенными системами, один из которых представляет собой архитектуру Z80, для которой нужен результат как unit8 (0-255), а другой, который хранит данные как uint32, это не может быть изменено. Я столкнулся с проблемой, когда мне нужна точность с плавающей запятой из расчета и ее необходимо было сохранить, но не смог передать ее как float, поэтому он был завернут в uint32. Однако при совместном использовании этих данных с микропроцессором Z80 он не может использовать библиотеку с плавающей запятой или uint32, ее необходимо отправить как uint8 (0-255). – DigitalNinja

ответ

4

Некоторые слегка протестированного кода, чтобы получить OP начал. Использует множество констант, позволяющих настраивать и проверять различные степени ошибок. ОП не рассматривал округление: от округления до ближайшего, к 0, или?. После усекает в сторону 0.

#include <stdint.h> 
#define MANTISSA_BIT_WIDTH 23 
#define BIASED_EXPO_MAX 255 
#define EXPO_BIAS 127 
#define SIGN_MASK 0x80000000 

unsigned DN_float_to_uint8(uint32_t x) { 
    if (x & SIGN_MASK) return 0; // negative 
    int expo = (int) (x >> MANTISSA_BIT_WIDTH); 
    if (expo == 0) return 0; // sub-normal 
    if (expo == BIASED_EXPO_MAX) return 255; // Infinity, NaN 
    expo -= EXPO_BIAS; 
    if (expo > 7) return 255; // too big 
    if (expo < 0) return 0; // wee number 
    uint32_t mask = ((uint32_t)1 << MANTISSA_BIT_WIDTH) - 1; 
    uint32_t mantissa = mask & x; 
    mantissa |= mask + 1; 
    mantissa >>= (MANTISSA_BIT_WIDTH - expo); 
    return mantissa; 
} 

#include <stdio.h> 
int main() { 
    printf("%u\n", DN_float_to_uint8(0x43246a00)); // 164 
    printf("%u\n", DN_float_to_uint8(0x437e0000)); // 254 
    printf("%u\n", DN_float_to_uint8(0x437f0000)); // 255 
    printf("%u\n", DN_float_to_uint8(0x43800000)); // 256 
    printf("%u\n", DN_float_to_uint8(0x3f7fffff)); // 0.99999994 
    printf("%u\n", DN_float_to_uint8(0x3f800000)); // 1 
    printf("%u\n", DN_float_to_uint8(0x40000000)); // 2 
    return 0; 
} 

Выход

164 
254 
255 
255 
0 
1 
2 

Полезные IEEE 754 Converter


Чтобы закруглить положительные значения до ближайшего (много способов справиться), завязки от нуля, просто посмотрите на последний бит должен быть сдвинут.

// mantissa >>= (MANTISSA_BIT_WIDTH - expo); 
    // return mantissa; 

    // shift as needed expect for 1 bit 
    mantissa >>= (MANTISSA_BIT_WIDTH - expo - 1); 
    // now shift last bit 
    mantissa = (mantissa + 1) >> 1; 
    // Handle special case 
    if (mantissa >= 256) mantissa = 255; 
    return mantissa; 
+0

Спасибо! Вы поднимаете хороший момент, и сначала я думал, что это не имеет большого значения, но, с другой стороны, я бы хотел, чтобы он округлялся до ближайшего целого целого.Я попытался выяснить, как изменить свой код, чтобы сделать это, но мой мозг чувствует себя как месиво, и я не вижу его. – DigitalNinja

+0

@ Цифровой ниндзя «круглый до ближайшего целого целого», а как насчет эквидистантного? например 3,5 или 4,5 или -0,5? – chux

+0

Округление на эквидистантных значениях получилось бы неплохо, но я не уверен в том, в каком направлении он идет, если один путь проще другого. – DigitalNinja

0

Предполагая поплавок хранящийся внутри uint32_t имеет тот же формат представления float типа на вашей архитектуре и ваших поплавки 32 битные sizeof(float)==4 (это вполне стандартный) вы можете сделать что-то подобное:

float *f; 

f = (float *)&fValue; 

if(*f <= 255 && *f >= 0) 
{ 
    result = (uint8_t) (*f); 
} 
else 
{ 
    // overflow/undeflow 
} 

Вы объявляете float указатель и указать его расположение uint32_t.

Затем возьмите значение указало пй указатель поплавка и попытку бросить в uint8_t


Я попробовал код и может сказать, что предположения выше истинны на Macos.

Например:

#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
    uint32_t fValue = 0x43246ADD; 
    uint8_t result=0; 

    float *f; 

    f = (float *)&fValue; 

    if(*f <= 255 && *f >= 0) 
    { 
     result = (uint8_t) (*f); 
    } 
    else 
    { 
     // overflow/undeflow 
    } 

    printf("%d\n", result); 
} 

Выходы:

164 
+0

Я очень ценю ваше время и усилия, и я уверен, что это сработает, но я не понял, что с обеих сторон я не могу использовать типы с плавающей запятой. – DigitalNinja

2

Итак, вы хотите, чтобы извлечь целую часть IEEE 754 single precision float, без операций с плавающей запятой доступны.

Все, что вам нужно, это несколько побитовых операций для извлечения экспоненты и мантиссы. Легко проверенный код. Этот код поддерживает значения со знаком и возвращает значение от -255 до 255 или INT_MIN, если абсолютное значение находится за пределами допустимого диапазона; отрегулируйте поддержку знака, поведения в случае переполнения и т. д. по мере необходимости.

int integer_part_of_single_float(uint32_t f) 
{ 
    uint32_t mantissa = (f & 0x7fffff) | 0x800000; 
    uint8_t exponent = f >> 23; 
    if (exponent < 127) { 
     return 0; 
    } else if (exponent >= 135) { 
     return INT_MIN; 
    } else { 
     unsigned absolute_value = mantissa >> (22 - (exponent - 128)); 
     return mantissa & 0x80000000 ? -absolute_value : absolute_value; 
    } 
} 
Смежные вопросы