【问题标题】:Using floats with sprintf() in embedded C在嵌入式 C 中使用带有 sprintf() 的浮点数
【发布时间】:2010-10-28 16:50:41
【问题描述】:

各位,我想知道float变量是否可以在sprintf()函数中使用。

比如,如果我们写:

sprintf(str,"adc_read = %d \n",adc_read);

其中adc_read是一个整数变量,它将存储字符串

"adc_read = 1023 \n"

str(假设adc_read = 1023

如何使用浮点变量代替整数?

【问题讨论】:

  • 你链接的是什么 C 库?
  • 请注意,您正在尝试格式化双精度。使用可变参数,编译器会自动将浮点值提升为双精度。
  • “嵌入式”,伙计们。那是关键。不管你的花哨的库在你有 GB 内存的机器上做什么都没关系 :-) 嵌入式平台通常会为了最小的内存占用而进行权衡。
  • 好吧,我正在使用 avr-gcc 编译器和 avr-libc 库

标签: c embedded floating-point printf


【解决方案1】:

由于您使用的是嵌入式平台,因此您很可能没有 printf() 样式函数的全部功能。

假设你有浮动(对于嵌入式的东西仍然不一定是给定的),你可以用类似的东西来模拟它:

char str[100];
float adc_read = 678.0123;

char *tmpSign = (adc_read < 0) ? "-" : "";
float tmpVal = (adc_read < 0) ? -adc_read : adc_read;

int tmpInt1 = tmpVal;                  // Get the integer (678).
float tmpFrac = tmpVal - tmpInt1;      // Get fraction (0.0123).
int tmpInt2 = trunc(tmpFrac * 10000);  // Turn into integer (123).

// Print as parts, note that you need 0-padding for fractional bit.

sprintf (str, "adc_read = %s%d.%04d\n", tmpSign, tmpInt1, tmpInt2);

您需要根据整数的大小限制小数点后的字符数。例如,对于 16 位有符号整数,您只能使用四位数字(9,999 是可以表示的最大 10 次幂)。

但是,有一些方法可以通过进一步处理小数部分,每次将其移动四位小数(并使用/减去整数部分),直到获得所需的精度。


更新:

您提到的最后一点是您在回复其他答案之一时使用了avr-gcc。我发现以下网页似乎描述了在 printf() 语句 here 中使用 %f 需要做什么。

正如我最初所怀疑的那样,您需要做一些额外的工作才能获得浮点支持。这是因为嵌入式东西很少需要浮点(至少我做过的东西都没有)。它涉及在您的 makefile 中设置额外的参数并与额外的库链接。

但是,由于需要处理通用输出格式,这可能会大大增加您的代码大小。如果您可以将浮点输出限制为小数点后 4 位或更少,我建议将我的代码变成一个函数并直接使用它 - 它可能占用更少的空间。

万一该链接消失,您需要做的是确保您的 gcc 命令具有 "-Wl,-u,vfprintf -lprintf_flt -lm"。这转换为:

  • 强制 vfprintf 最初未定义(以便链接器必须解析它)。
  • 指定浮点printf()库进行搜索。
  • 指定要搜索的数学库。

【讨论】:

  • 给出编译错误错误:第 5 行上述代码中“int”之前的预期表达式。
  • @aditya,看看 cmets,他们现在展示了如何提取数字。
  • 请注意,通过减去整数部分并乘以 10000 来获得小数部分来操作浮点数可能会更改某些最低有效数字的值
  • 对于 gcc / stm32,添加这个对我有用:LDFLAGS += -u _printf_float
【解决方案2】:

这样的事情真的很简单吗:

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

char str[10];
float adc_read = 678.0123;

dtostrf( adc_read, 3, 4, temp );
sprintf(str,"adc_read = %10s \n", temp);
printf(temp);

【讨论】:

  • 是的,经过数小时的搜索,它对我很有用,谢谢
  • 这更容易,但如果你的目标是微型微控制器,因此无法嵌入 stdlib。
【解决方案3】:

是的,你可以。但是,这取决于您链接的 C 库,您需要了解后果。

由于您正在为嵌入式应用程序编程,因此请意识到浮点支持已针对许多嵌入式架构进行了仿真。在这种浮点支持中进行编译最终会显着增加可执行文件的大小。

【讨论】:

    【解决方案4】:

    使用%f 修饰符:

    sprintf (str, "adc_read = %f\n", adc_read);
    

    例如:

    #include <stdio.h>
    
    int main (void) 
    {
        float x = 2.5;
        char y[200];
    
        sprintf(y, "x = %f\n", x);
        printf(y);
        return 0;
    }
    

    产生这个:

    x = 2.500000

    【讨论】:

    • 我做了,但它没有工作它打印一个?字符而不是字符串
    【解决方案5】:

    是的,当然,花车并没有什么特别之处。您可以像在 printf() 中对浮点数和任何其他数据类型一样使用格式字符串。

    编辑 我试过这个示例代码:

    float x = 0.61;
    char buf[10];
    sprintf(buf, "Test=%.2f", x);
    printf(buf);
    

    输出为:Test=0.61

    【讨论】:

    • 我做了,但它没有工作它打印一个?字符而不是字符串
    • 查看我的示例代码的编辑。我感觉你的缓冲区“str”太小了。尝试使用类似的 %.2f 来设置精度。
    • 我的缓冲区大小实际上是 100,我在串口上打印它,所以我认为问题在于通过 UART 传输浮点变量
    • @aditya,sprintf(如果可用)将生成标准 ASCII,因此输出没有问题。更有可能是您的嵌入式平台的限制。
    【解决方案6】:

    不要期望 sprintf(或任何其他带有可变参数的函数)会自动转换任何内容。编译器不会尝试读取格式字符串并为您进行转换;在运行时,sp​​rintf 没有可用的元信息来确定堆栈上的内容;它只是弹出字节并将它们解释为格式字符串给定的。 sprintf(myvar, "%0", 0);立即出现段错误。

    所以:格式字符串和其他参数必须匹配!

    【讨论】:

      【解决方案7】:

      查看适用于您平台的 sprintf 的文档。它通常是 %f 或 %e。您唯一能找到明确答案的地方就是文档……如果没有文档,您所能做的就是联系供应商。

      它是什么平台?有人可能已经知道文档在哪里... :)

      【讨论】:

      • 它是 Atmega128 微控制器,编译器是 avr-gcc。感谢您的回复,但 Pax 给出的答案非常有效。
      【解决方案8】:

      是的,也不是。尽管其他一些回复说,C 编译器sprintf() 和所有其他可变参数函数执行转换,如下所示:

      • char => int
      • short => int
      • float => double

      (以及上述整数类型的有符号/无符号变体)

      它这样做正是因为没有它sprintf()(和其他print()-family 函数)将无法使用。 (当然是they're pretty unusable as it is。)

      但是您不能假设任何其他转换,并且您的代码将具有未定义的行为 - 阅读:崩溃! - 如果你这样做了。

      【讨论】:

      • 编译器需要执行转换cast 是程序员明确要求的。
      • “无法使用”是一个非常强烈的词。 printf() 系列非常有用,但并不完美。它有明显的类型安全缺陷,但是编译器可以很容易地检查你在 99% 的用例中传递的参数的类型,并给你一个警告/错误(参见 gcc 的 -Wformat 等)。
      • Adam:“99%”也是一个非常强调的主张,=P。 一些 编译器可以进行这种检查,但只有当格式参数是包含在 printf() 语句中的字符串文字时才能这样做。如果格式字符串是从别处获得的,或者是原位合成的,那么编译器就无济于事了。
      • @AdamRosenfield:恕我直言,一个重大的悲剧是 ANSI 不允许可变参数函数指定浮点参数类型并将所有浮点参数强制转换为这些参数类型。语言处理浮点类型的一种简洁方法是将long double定义为通过将两个double值相乘产生的类型,将long float定义为通过乘以float值产生的类型(这可能与floatlong double 相同,或者可能是针对计算优化的形式)。 IEEE-754 扩展精度类型背后的一个主要目的是......
      • ...具有 16 位和 32 位处理器的将 64 位双精度值解压缩为更大的 80 位形式,对它们执行一系列操作,然后将结果,将比必须在每次操作后执行解包和重新打包操作更快。为此,将两个double 值相乘的结果应该是long double。让它伪装成 double 会产生混乱的浮点语义。
      【解决方案9】:

      不要这样做; C/C++ 中的整数总是向下舍入,因此无需使用 floor 函数。

      char str[100]; 
      int d1 = value;
      

      更好用

      int d1 = (int)(floor(value));
      

      那么您将不会对整数部分进行四舍五入(68.9999999999999999 变为 69.00..)。 很难避免使用 68.09999847 而不是 68.1 - 任何浮点格式都具有有限的精度。

      【讨论】:

        【解决方案10】:

        %g 可以这样做:

        #include <stdio.h>
        int main() {
          float w = 234.567;
          char x[__SIZEOF_FLOAT__];
          sprintf(x, "%g", w);
          puts(x);
        }
        

        【讨论】:

          【解决方案11】:

          许多嵌入式系统都有一个有限的 snprintf 函数,它不能处理浮点数。我写了这个,它相当有效地完成了这个把戏。我选择使用 64 位无符号整数来处理大浮点数,因此请随意将它们减少到 16 位,或者在资源有限的情况下满足您的任何需求。

          #include <stdio.h>   // for uint64_t support.
          
          
          int  snprintf_fp( char destination[], size_t available_chars, int decimal_digits,
                            char tail[], float source_number )
          {
              int   chars_used  = 0;    // This will be returned.
          
          
              if ( available_chars > 0 )
              {
                  // Handle a negative sign.
                  if ( source_number < 0 )
                  {
                      // Make it positive
                      source_number = 0 - source_number;
                      destination[ 0 ] = '-';
                      ++chars_used;
                  }
          
                  // Handle rounding
                  uint64_t zeros = 1;
                  for ( int i = decimal_digits; i > 0; --i )
                      zeros *= 10;
          
                  uint64_t source_num = (uint64_t)( ( source_number * (float)zeros ) + 0.5f );
          
                  // Determine sliding divider max position.
                  uint64_t  div_amount = zeros;       // Give it a head start
                  while ( ( div_amount * 10 ) <= source_num )
                      div_amount *= 10;
          
                  // Process the digits
                  while ( div_amount > 0 )
                  {
                      uint64_t whole_number = source_num / div_amount;
                      if ( chars_used < (int)available_chars )
                      {
                          destination[ chars_used ] = '0' + (char)whole_number;
                          ++chars_used;
          
                          if ( ( div_amount == zeros ) && ( zeros > 1 ) )
                          {
                              destination[ chars_used ] = '.';
                              ++chars_used;
                          }
                      }
                      source_num -= ( whole_number * div_amount );
                      div_amount /= 10;
                  }
          
          
                  // Store the zero.
                  destination[ chars_used ] = 0;
          
                  // See if a tail was specified.
                  size_t tail_len = strlen( tail );
          
                  if ( ( tail_len > 0 ) && ( tail_len + chars_used < available_chars ) )
                  {
                      for ( size_t i = 0; i <= tail_len; ++i )
                          destination[ chars_used + i ] = tail[ i ];
                      chars_used += tail_len;
                  }
              }
          
              return chars_used;
          }
          
          main()
          {
              #define TEMP_BUFFER_SIZE 30
              char temp_buffer[ TEMP_BUFFER_SIZE ];
              char  degrees_c[] = { (char)248, 'C', 0 };
              float  float_temperature = 26.845f;
          
              int len = snprintf_fp( temp_buffer, TEMP_BUFFER_SIZE, 2, degrees_c, float_temperature );
          }
          

          【讨论】:

          • 即使是可以支持全功能 printf 的实现通常也可以省略它,如果不需要的话,可以节省 大量 代码空间。除非程序需要以指数格式输出数字,否则通常最好计算一个 uint64_t 值,该值是正确值的 10 倍,将其转换为字符串,然后插入小数点。
          • 当然,但是转换一个使用浮动的现有项目需要太多的手术。即使将浮点数乘以(例如)100,然后将其类型转换为整数也需要一些舍入、负号处理、处理小数点前数字的循环、存储小数、存储附加字符、前导零、后置零和添加后缀仍然是相当多的代码 - 所有这些都在我的例程中处理,与实际的浮点数一起使用。
          • 我只是想让您提到像您这样的技术不仅对缺乏全功能 printf 的实现有用,而且对代码空间有限的实现也有用。
          【解决方案12】:

          类似于上面的paxdiablo。 此代码插入到更广泛的应用程序中,可与 STM32 NUCLEO-F446RE 完美配合。

          #include <stdio.h>
          #include <math.h>
          #include <string.h>
          void IntegFract(char *pcIntegStr, char *pcFractStr, double dbValue, int iPrecis);
          
          main()
          {
             char acIntegStr[9], acFractStr[9], char counter_buff[30];
             double seconds_passed = 123.0567;
             IntegFract(acIntegStr, acFractStr, seconds_passed, 3);
             sprintf(counter_buff, "Time: %s.%s Sec", acIntegStr, acFractStr);
          }
          
          void IntegFract(char *pcIntegStr, char *pcFractStr, double dbValue, int 
          iPrecis)
          {
              int iIntegValue = dbValue;
              int iFractValue = (dbValue - iIntegValue) * pow(10, iPrecis);
              itoa(iIntegValue, pcIntegStr, 10);
              itoa(iFractValue, pcFractStr, 10);
              size_t length = strlen(pcFractStr);
              char acTemp[9] = "";
              while (length < iPrecis)
              {
                  strcat(acTemp, "0");
                  length++;
              }
              strcat(acTemp, pcFractStr);
              strcpy(pcFractStr, acTemp);
          }
          

          counter_buff 将包含 123.056 。

          【讨论】:

            【解决方案13】:

            是的,使用 %f

            【讨论】:

            • 它打印一个 ?字符而不是字符串。
            • 您是否为 str 正确分配空间?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-02
            • 1970-01-01
            • 2010-09-25
            • 1970-01-01
            • 2020-09-14
            • 1970-01-01
            相关资源
            最近更新 更多