将uint32浮点表示转换为uint8

4
不多加细节地讲,我有两个嵌入式系统,都无法使用浮点数库。它们之间有一个移动应用程序,其中执行一些计算。一个计算需要保持精度。这个值以字节数组形式通过蓝牙发送给客户端。当它被接收后,我将其存储为uint32。然后将此值再次与移动应用程序共享,在那里需要精度。这里没有问题,因为我可以使用Java的ByteBuffer类。问题是我还需要将这个值作为uint8与Z80微处理器共享,因为它通过UART传输,并且被用作ADC计数(0-255),所以会失去精度(但在这一端不需要精度)。所以我在我的移动应用程序中使用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位字读取,并将其存储在uint32中。在调试器中查看此uint32将显示正确值,如上所述,我能够将此uint32放回字节数组中并使用Java的ByteBuffer类发送到移动应用程序并读取它。这很好用,我得到了正确的浮点值。

问题是我需要将此浮点表示的整数部分作为uint8通过UART发送到Z80微处理器,因为它用作从0-255的ADC计数。

那么,如何将已包装成uint32(小端字节顺序)的浮点值转换为uint8,以便失去精度?我还知道范围是0-255,这意味着永远不会有大于255.0要转换的内容。

例如,如果我有这个:

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

我该如何做到这一点而不使用float:

uint8 result = 164;


4
需要了解特定的浮点数编码格式,这并非C语言所规定。这是一个例子。而整数部分很可能无法适应8位。 - Weather Vane
1
你能解释一下为什么你在uint32中存储了一个浮点值吗?为什么你使用的是uint32而不是在<stdint.h>中定义的标准uint32_t - Keith Thompson
1
@KeithThompson 当然,我正在使用两个嵌入式系统,其中一个是需要结果作为unit8(0-255)的Z80架构,而另一个则将数据存储为uint32,无法更改。我遇到了一个问题,需要从计算中获得浮点精度并需要存储它,但无法将其作为浮点数传输,因此它被包装成uint32。但是,在向Z80微处理器返回此数据时,它不能使用浮点库或uint32,必须将其作为uint8(0-255)发送。 - DigitalNinja
浮点值在 uint32 中如何表示?哪些位表示指数、尾数和符号,以及如何表示? - Keith Thompson
如果代码从32位无符号类型开始,则字节序不是问题。如果代码从4字节数组开始,则存在字节序问题。float的字节序通常与uint32_t相同,但未指定为如此。当然,整个float格式不是C特定的。最好像binary32这样声明FP格式。 - chux - Reinstate Monica
显示剩余3条评论
3个回答

4

以下是一些经过轻微测试的代码,可帮助OP入门。使用了许多常量以允许自定义和各种程度的错误检查。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转换器


将正数四舍五入到最接近的值(有很多方法可以处理),并且在遇到“5”的情况下要远离零,只需查看要移出的最后一位即可。

  // 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;

谢谢!你提出了一个很好的观点,一开始我认为这并不重要,但仔细想想,我希望它能四舍五入到最近的整数。我尝试着想办法修改你的代码来实现这个功能,但我的大脑感觉像泥巴一样,我看不到它。 - DigitalNinja
@DigitalNinja "四舍五入到最近的整数",那么当距离相等时怎么办?例如3.5或4.5或-0.5? - chux - Reinstate Monica
将等距值四舍五入可能会很好,但如果有一种方法比另一种更容易,我并不是特别关心它朝哪个方向走。 - DigitalNinja
我修改得很接近了。再次感谢!你真是个聪明的家伙 ;) - DigitalNinja

2

想要从一个IEEE 754单精度浮点数中提取整数部分,但不能使用浮点运算。

你只需要进行几个位运算即可提取指数和尾数。以下是简单测试过的代码。该代码支持有符号数值,并返回介于-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;
    }
}

0
假设在您的架构中,存储在 uint32_t 中的 float 具有与 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 的位置。
然后获取 float 指针所指向的值,并尝试将其转换为 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

非常感谢您的时间和努力,我相信这会奏效,但我没意识到无法在任一侧使用浮点类型。 - DigitalNinja

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接