Is there a good way of converting an uint8_t array into a single float value that doesn't involve a loop?

huangapple go评论51阅读模式
英文:

Is there a good way of converting an uint8_t array into a single float value that doesn't involve a loop?

问题

将一个uint8_t数组转换为单个浮点值有更好的方法吗?

我对此非常不熟悉。
我目前正在使用ESP32-S3-WROOM-1从NEO-6M GPS接收UART数据。
IDE:Visual Studio Code

认为我的代码(我附在c和h文件下面)应该获取UART数据并将其放入数组中。对于时间,我可以只设置为两位,但对于像纬度和经度这样更长的值,我将不得不让代码相互相加和相乘,直到达到小数点,然后稍后再添加其余的值。

基本上,逐字节进行。

char* Post_Time_String = strstr(String_Input, ",");
F_Extract_Until_Comma(Post_Time_String, Lat_Val);
for(int itr=0; itr<sizeof(Lat_Val),itr++)
{
    if(Lat_Val[itr] != ".")
    {
        gps_info->GPS_POSITION->latitude = (10 * gps_info->GPS_POSITION->latitude) + Lat_Val[itr];
    }
}

我一直在尝试找到更好的方法,因为我觉得这样非常低效和慢。如果我可以像字符串一样获取上面的内容,那么我可以使用stof(),我想?

供参考,以下是我目前正在工作的头文件和主文件。工作正在进行中,肯定有问题,我还找不到。
NMEA_PARSE_CODE.c

#include <stdio.h>
#include "NMEA_PARSE_CODE.h"

void F_FIND_COMMA(char *String_Input, char* String_Output)
{
    char *String_Output = strstr(String_Input, ",");
    return String_Output;
}

void F_Extract_Until_Comma(char *String_Input, uint8_t* Extracted_Output[])
{
    uint8_t String_Length = strlen(String_Input);
    uint8_t* F_Buffer[20] = {NULL};
    char* comma_parse_string = F_FIND_COMMA(String_Input); //Get string after comma

    uint8_t comma_parse_string_length = strlen(comma_parse_string); //Get length of string after comma

    for (uint8_t itr = 0; itr < comma_parse_string_length, itr++)
    {
        if(comma_parse_string[itr] == ",")
        {
            break;
        }
        F_Buffer[itr] = comma_parse_string[itr];
        Extracted_Output[itr] = comma_parse_string[itr];
    }
}

void F_U8_to_F(uint8_t Input_val, uint8_t pos, float* Output)
{
    float OutputHold;
    OutputHold = 10*Input_val[pos];
    OutputHold = OutputHold + Input_val[pos+1];
    Output = OutputHold;
}

void F_NMEA_TIME_CONVERT(uint8_t Input_Val, NMEA_GPS gps_info)
{
    F_U8_to_F(timeval, 0, gps_info->GPS_TIME->nmea_hours);
    F_U8_to_F(timeval, 2, gps_info->GPS_TIME->nmea_minutes);
    F_U8_to_F(timeval, 4, gps_info->GPS_TIME->nmea_seconds);
}

void F_GGA_Parse(uint8_t* String_Input, NMEA_GPS gps_info)
{
    uint8_t time_value[12], Lat_Val[12]; //从String_Input获取并转换为浮点值
    F_Extract_Until_Comma(String_Input, time_value);
    F_NMEA_TIME_CONVERT(time_value, gps_info);

    //时间结束
    char* Post_Time_String = strstr(String_Input, ",");
    F_Extract_Until_Comma(Post_Time_String, Lat_Val);
    for(int itr=0; itr<sizeof(Lat_Val),itr++)
    {
        if(Lat_Val[itr] != ".")
        {
            gps_info->GPS_POSITION->latitude = (10 * gps_info->GPS_POSITION->latitude) + Lat_Val[itr];
        }
    }
    F_U8_to_F(Lat_Val,0,gps_info);
}

char F_Extract_GPS_Data(char * raw_string_input, NMEA_GPS* gps_info)
{
    char* GPS_ID_FOUND = strstr(raw_string_input, "$GP");
    uint8_t Comma_Rounds = 0;

    if(NULL == GPS_ID_FOUND)
    {
        return NULL;
    }
    else
    {
        if(strstr(GPS_ID_FOUND, "GGA"))
        {
            gps_info->GPS_TYPE = GGA;
            gps_info->GGA_Flag = YES;
            Comma_Rounds = 14;
        }
        else if(strstr(GPS_ID_FOUND,"RMC"))
        {
            gps_info->GPS_TYPE = RMC;
            gps_info->RMC_Flag = YES;
            Comma_Rounds = 11;
        }
        else if(strstr(GPS_ID_FOUND,"VTG"))
        {
            gps_info->GPS_TYPE = VTG;
            gps_info->VTG_Flag = YES;
            Comma_Rounds = 9;
        }

        if(gps_info->GPS_TYPE == 1)
        {
            F_GGA_Parse(GPS_ID_FOUND, gps_info);
        }
    }
}

NMEA_PARSE_CODE.h

#include <stdio.h>

typedef enum
{
    NO=0,
    YES=1
} NMEA_ID_GGA;

typedef enum
{
    NO=0,
    YES=1
} NMEA_ID_RMC;

typedef enum
{
    NO=0,
    YES=1
} NMEA_ID_VTG;

typedef enum 
{
    GGA=1,
    RMC,
    VTG

} NMEA_ID;

typedef struct
{
    float nmea_hours;
    float nmea_minutes;
    float nmea_seconds;
    float nmea_thousands;
} NMEA_UTC_TIME;

typedef struct
{
    uint8_t nmea_year;
    uint8_t nmea_month;
    uint8_t nmea_day;
} NMEA_DATE;

typedef struct
{
    float longitude;
    float latitude;
} NMEA_POSITION;

typedef struct
{
    char NORTHSOUTH;
    char EASTWEST;
    float course; //以度为单位
    float knot_speed; //以节为单位
    float meter_speed; //以米/秒为单位
} NMEA_VELOCITY;

typedef struct
{
    NMEA_ID GPS_TYPE;
    NMEA_ID_GGA GGA_Flag;
    NMEA_ID_RMC RMC_Flag;
    NMEA_ID_VTG VTG_Flag;
    NMEA_UTC_TIME GPS_TIME;
    NMEA_DATE GPS_DATE;
    NMEA_POSITION GPS_POSITION;
    NMEA_VELOCITY GPS_VELOCITY;
} NMEA_GPS;

typedef struct
{
    NMEA_ID ID;
    NMEA_UTC_TIME TIME;
} NMEA_GGA;

逐字节进行,将现有的浮点值乘以10,然后添加新的字节。重复此过程直到

英文:

Is there a better way of converting an uint8_t array into a single float value?

I am extremely new at using this.
I am currently using a ESP32-S3-WROOM-1 to take UART data from a NEO-6M GPS.
IDE: Visual Studio Code

I think my code (which I attached below the c and h file) should take the UART data and place it into an array. For time I can just set it two by two, but for longer values like Latitude and Longitude, I will have to have the code add and multiply each other until it reaches the decimal point, then add the rest of the values later.

Basically, going byte by byte.

char* Post_Time_String = strstr(String_Input, &quot;,&quot;);
F_Extract_Until_Comma(Post_Time_String, Lat_Val);
for(int itr=0; itr&lt;sizeof(Lat_Val),itr++)
{
if(Lat_Val[itr] != &quot;.&quot;)
{
gps_info-&gt;GPS_POSITION-&gt;latitude = (10 * gps_info-&gt;GPS_POSITION-&gt;latitude) + Lat_Val[itr];
}
}

I have been trying to find a better way of doing this, because I get the feeling that this is very inefficient and slow. If I can get the above like a string, then I can use strof() I think?

For reference the header and main files I am currently working on. WIP and definitely has issues that I can't find yet.
NMEA_PARSE_CODE.c

#include &lt;stdio.h&gt;
#include &lt;NMEA_PARSE_CODE.h&gt;
void F_FIND_COMMA(char *String_Input, char* String_Output)
{
char *String_Output = strstr(String_Input, &quot;,&quot;);
return String_Output;
}
void F_Extract_Until_Comma(char *String_Input, uint8_t* Extracted_Output[])
{
uint8_t String_Length = strlen(String_Input);
uint8_t* F_Buffer[20] = {NULL};
char* comma_parse_string = F_FIND_COMMA(String_Input); //Get string after comma
//Find comma and extract data until next comma
uint8_t comma_parse_string_length = strlen(comma_parse_string); //Get length of string after comma
for (uint8_t itr = 0; itr &lt; comma_parse_string_length, itr++)
{
if(comma_parse_string[itr] == &quot;,&quot;)
{
break;
}
F_Buffer[itr] = comma_parse_string[itr];
Extracted_Output[itr] = comma_parse_string[itr];
}
}
void F_U8_to_F(uint8_t Input_val, uint8_t pos, float* Output)
{
float OutputHold;
OutputHold = 10*Input_val[pos];
OutputHold = OutputHold + Input_val[pos+1];
Output = OutputHold;
}
void F_NMEA_TIME_CONVERT(uint8_t Input_Val, NMEA_GPS gps_info)
{
F_U8_to_F(timeval, 0, gps_info-&gt;GPS_TIME-&gt;nmea_hours);
F_U8_to_F(timeval, 2, gps_info-&gt;GPS_TIME-&gt;nmea_minutes);
F_U8_to_F(timeval, 4, gps_info-&gt;GPS_TIME-&gt;nmea_seconds);
}
void F_GGA_Parse(uint8_t* String_Input, NMEA_GPS gps_info)
{
//TIME, Lat, N/S, Long, E/W, Fix, Sat#, HDOP, Alt, unit, Geoid, unit,  .........
strof();
uint8_t time_value[12], Lat_Val[12]; //obtain from String_Input and to convert into float value
F_Extract_Until_Comma(String_Input, time_value);
F_NMEA_TIME_CONVERT(time_value, gps_info)
//time end
char* Post_Time_String = strstr(String_Input, &quot;,&quot;);
F_Extract_Until_Comma(Post_Time_String, Lat_Val);
for(int itr=0; itr&lt;sizeof(Lat_Val),itr++)
{
if(Lat_Val[itr] != &quot;.&quot;)
{
gps_info-&gt;GPS_POSITION-&gt;latitude = (10 * gps_info-&gt;GPS_POSITION-&gt;latitude) + Lat_Val[itr];
}
}
F_U8_to_F(Lat_Val,0,gps_info);
}
char F_Extract_GPS_Data(char * raw_string_input, NMEA_GPS* gps_info)
{
char* GPS_ID_FOUND = strstr(raw_string_input, &quot;$GP&quot;);
uint8_t Comma_Rounds = 0;
if(NULL == GPS_ID_FOUND)
{
return NULL;
}
else
{
if(strstr(GPS_ID_FOUND, &quot;GGA&quot;))
{
gps_info-&gt;GPS_TYPE = GGA;
gps_info-&gt;GGA_Flag = YES;
Comma_Rounds = 14;
}
else if(strstr(GPS_ID_FOUND,&quot;RMC&quot;))
{
gps_info-&gt;GPS_TYPE = RMC;
gps_info-&gt;RMC_Flag = YES;
Comma_Rounds = 11;
}
else if(strstr(GPS_ID_FOUND,&quot;VTG&quot;))
{
gps_info-&gt;GPS_TYPE = VTG;
gps_info-&gt;VTG_Flag = YES;
Comma_Rounds = 9;
}
if(gps_info-&gt;GPS_TYPE == 1)
{
F_GGA_Parse(GPS_ID_FOUND, gps_info);
}
// if(GPS_ID_FOUND[0])
}
}

NMEA_PARSE_CODE.h

#include &lt;stdio.h&gt;
typedef enum
{
NO=0,
YES=1
} NMEA_ID_GGA;
typedef enum
{
NO=0,
YES=1
} NMEA_ID_RMC;
typedef enum
{
NO=0,
YES=1
} NMEA_ID_VTG;
typedef enum 
{
GGA=1,
RMC,
VTG
} NMEA_ID;
typedef struct
{
float nmea_hours;
float nmea_minutes;
float nmea_seconds;
float nmea_thousands;
} NMEA_UTC_TIME;
typedef struct
{
uint8_t nmea_year;
uint8_t nmea_month;
uint8_t nmea_day;
} NMEA_DATE;
typedef struct
{
float longitude;
float latitude;
} NMEA_POSITION;
typedef struct
{
char NORTHSOUTH;
char EASTWEST;
float course; //In degrees
float knot_speed; //In knots
float meter_speed; //In meters per second
} NMEA_VELOCITY;
typedef struct
{
NMEA_ID GPS_TYPE;
NMEA_ID_GGA GGA_Flag;
NMEA_ID_RMC RMC_Flag;
NMEA_ID_VTG VTG_Flag;
NMEA_UTC_TIME GPS_TIME;
NMEA_DATE GPS_DATE;
NMEA_POSITION GPS_POSITION;
NMEA_VELOCITY GPS_VELOCITY;
} NMEA_GPS;
typedef struct
{
NMEA_ID ID;
NMEA_UTC_TIME TIME;
} NMEA_GGA;

Going byte by byte, multiply the existing float value by 10 before adding the new byte. Repeat until end of length is reached. The code compiles somehow in Visual Studio Code, but that isn't saying much. VSC isn't showing me any errors either.

答案1

得分: 3

针对您提出的问题,答案是,没有一种方法可以将像42.23234这样的字符串转换为float,而不涉及循环。此外,仅仅是“将现有的浮点值乘以10,然后添加新字节”对于浮点数值来说完全不足够,因为它不处理小数点或小数点后的小数位。

将字符串转换为浮点数值的简单方法(顺便说一下,在您的代码中不涉及任何循环)是调用预先编写的库函数,如atofstrtodsscanf。在这里强烈建议使用预先编写的库函数,因为正确地转换浮点字符串在一般情况下是一个相当困难的问题,最好让别人来完成这项工作。

这里有一个简单的示例:

char *p = "42.23234,N,071.34681,E"; /* 可能是NMEA字符串的一部分 */
char *p2;
double lat = strtod(p, &p2);
printf("lat = %f, rest = %s\n", lat, p2);

这将打印出:

lat = 42.232340, rest = ,N,071.34681,E

这是如何工作的:strtod将您给它的字符串的第一部分转换为double,然后通过一个“引用”指针参数返回剩下的字符串的指针,因为它遇到了一个非数字字符,在本例中是第一个逗号。

[但请注意,在NMEA中,42.23234并不表示42.23234度。实际上,它是42度,23.234分钟,计算出为42.3872333度。所以结果是,你不能简单地调用strtod来获取你的纬度和经度。]

尽管不是你提出的问题,但你的代码其余部分有很多问题。我建议从一个更小、更简单的问题开始。或者如果你需要解析NMEA字符串,看看是否可以找到一些适合你需求的预先编写的代码,因为那里有大量的代码可用。

如果你确实想编写自己的NMEA解析器,我建议将问题视为两个完全独立的部分:

  1. 将一句话拆分成逗号分隔的字段
  2. 根据你解析的特定字符串($GPGGA、$GPVTG、$GPZDA等),单独处理每个字段

但是,试图一次移动字符串一个字符,同时转换字段内容并查找逗号,往往会导致混乱不堪。

其他杂项注意事项:

  1. 类型float的精度不足以表示全球纬度和经度。使用double。(实际上,一个很好的通用规则是,类型float实际上没有足够的精度来表示任何东西,所以你应该始终使用double。)
  2. 如果你有一个函数void F_U8_to_F(uint8_t Input_val, uint8_t pos, float* Output),该函数应该通过引用参数Output返回一个值,你需要使用*Output = OutputHold;进行赋值。(你的编译器应该已经警告你了。)
  3. 当你调用那个函数F_U8_to_F,该函数应该通过引用参数返回一个值时,你需要传递一个指向你要填充的值的指针:F_U8_to_F(timeval, 2, &gps_info->GPS_TIME->nmea_minutes);
  4. 我建议对nmea_hoursnmea_minutes使用int(而不是float)。 (如果你要分别保留nmea_thousands,你不需要float,也不需要nmea_seconds。)但根据程序的其他需求,你可能会发现将其转换为更适合计算的时间格式更有用,而不是分别处理小时、分钟和秒字段。
英文:

In answer to the question you asked, no, there is not a way of converting a string like 42.23234 to a float that does not involve a loop, somewhere. Moreover, simply "multiply the existing float value by 10 before adding the new byte" is completely insufficient for floating-point values, because it doesn't handle the decimal point or the fractional digits after it.

The simple way to convert a string to a floating-point value (which, incidentally, does not involve any loops in your code) is to call a prewritten library function such as atof, strtod, or sscanf. And using a prewritten library function is highly, highly recommended here, because properly converting floating-point strings is quite a hard problem in general, and it's much better to let someone else do that work.

Here's a simple example:

char *p = &quot;42.23234,N,071.34681,E&quot;;    /* might be part of a NMEA string */
char *p2;
double lat = strtod(p, &amp;p2);
printf(&quot;lat = %f, rest = %s\n&quot;, lat, p2);

This prints

lat = 42.232340, rest = ,N,071.34681,E

The way this works is that strtod converts the first part of the string you give it to a double, then returns you (via a "reference" pointer parameter) a pointer to the rest of the string, that it didn't convert, because it ran into a non-numeric character, in this case the first comma.

[Beware, though, that in NMEA, 42.23234 does not mean 42.23234 degrees. It's actually 42 degrees, 23.234 minutes, which works out to 42.3872333 degrees. So it turns out you're not going to be able to use a straightforward call to strtod to get your latitudes and longitudes, after all.]

Although not the question you asked, the rest of your code has many, many problems beyond that of converting floating-point strings. I recommend starting with a smaller, simpler problem. Or if you need to parse NMEA strings today, see if you can find some prewritten code that fits your needs — there's gobs and gobs of it out there.

If you do want to write your own NMEA parser, I recommend treating the problem as two completely separate parts:

  1. Break a sentence up into comma-separated fields
  2. Handle each field separately, depending on the particular string you're parsing ($GPGGA, $GPVTG, $GPZDA, etc.)

But trying to move along the string one character at a time, simultaneously converting field contents and looking for commas, tends to lead to an unholy mess.

One decent way to break a string up into comma-separated fields is to use the library function strtok.

Miscellaneous other notes:

  1. Type float does not have enough precision for global latitudes and longitudes. Use double. (Actually, a fine general rule is that type float doesn't really have enough precision for anything, so you should always use double.)
  2. If you have a function void F_U8_to_F(uint8_t Input_val, uint8_t pos, float* Output) that's supposed to return a value via the reference parameter Output, you need to assign that using *Output = OutputHold;. (Your compiler should have warned you about this.)
  3. When you're calling that function F_U8_to_F that's supposed to return a value via a reference parameter, you need to pass a pointer to the value you want to fill in: F_U8_to_F(timeval, 2, &amp;gps_info-&gt;GPS_TIME-&gt;nmea_minutes);
  4. I recommend using int (not float) for nmea_hours and nmea_minutes. (And if you're going to carry nmea_thousands separately, you don't need a float for it or for nmea_seconds, either.) But depending on the needs of the rest of the program, you might find it more useful to convert to a more computation-friendly time format, not separate hours, minutes, and seconds fields.

答案2

得分: 0

继续参考@Steve Summit回答,利用那里提供的关于NMEA格式的信息:

我个人建议完全放弃浮点数,而是退回到固定逗号算术 – 这意味着您不表示完整的度数,而是适当的子精度。这样可以使所有内部处理变得更加容易,并避免任何潜在的舍入问题(您可能无法得到与之前读取的完全相同的值)。

在给定的情况下,对我来说,使用与NMEA相同的子精度似乎是有意义的:

> 但要注意,在NMEA中,42.23234并不表示42.23234度。实际上是42度,23.234分钟,这等于42.3872333度。
<br />[Steve Summit]

如果这个精度是固定的(始终是千分之一的分钟,所以“毫分钟”) 并且 包括尾随的零(最简单的情况),那么您可以简单地解析两个整数并应用适当的因子:

char* nmea = ...;
char* end;
signed long degrees = strtol(nmea, &amp;end, 10);

现在 *end 应该包含一个句点。如果没有,那么小数部分丢失了,您可以将度数归一化为相应的单位,例如“毫分钟”为60000。

有了小数部分,我们现在可以继续:

nmea = end + 1; // 假设我们在一个函数中,因此无论如何都有一个指针的副本
signed long milliMinutes = strtol(nmea, &amp;end, 10);

如果保证包括尾随的零,那么 end - nmea 现在应该等于5。如果不是 – 好吧,您需要扩展:

ptrdiff_t length = end - nmea;
for(; length &lt; 5; ++length) // 或者任何其他所需的精度
{
    milliMinutes *= 10;
}

这是可以接受的,即使情况似乎是您有太多的数字(那么您选择了一个太小的精度)。为了涵盖这种情况,您需要进行除法运算 – 我们可以记住最后一个被丢弃的数字以确定四舍五入:

int digit = 0;
for(; length &gt; 5; --length)
{
    digit = milliMinutes % 10;
    milliMinutes /= 10;
}
milliMinutes += digit &gt;= 5;

实际上不需要包围的 if,循环内的条件涵盖了相应的逆情况。现在最后您可以组合,但需要考虑两个数字的符号可能不同:

if(degrees &lt; 0)
{
    milliMinutes = -milliMinutes;
}
milliMinutes += degrees + 60000;

或者,如果您更喜欢无分支的方式:

milliMinutes
= degrees * 60000
+ milliMinutes * (degrees &gt;= 0)
- milliMinutes * (degrees &lt; 0);

现在您有一个单一的整数值,没有浮点数问题 - 适用于内部处理。

唯一的缺点:当以任何方式输出给用户时,您需要再次将该值转换为NMEA格式;不过这并不是什么大问题:

printf("%l.%.5l", milliMinutes / 10000, milliMinutes % 10000);

当然,您也可以将其调整为其他精度;)

英文:

Extending on @Steve Summit's answer, profiting from the information about NMEA format given there:

I personally recommend dropping floating point entirely and instead fall back to fixed-comma-arithmetic – meaning that you do not represent full degrees, but some appropriate sub-precision. This makes all internal handling much easier and avoids any potential rounding issues (where you might not get back exactly the same value as you have been reading before).

In given case it appears meaningful to me to use the same sub-precision as NMEA:

> Beware, though, that in NMEA, 42.23234 does not mean 42.23234 degrees. It's actually 42 degrees, 23.234 minutes, which works out to 42.3872333 degrees.
<br />[Steve Summit]

If this precision is fix (always thousands of minutes, so 'milli-minutes') and trailing zeros are included (the most simple case), then you can simply parse two integers and apply an appropriate factor:

char* nmea = ...;
char* end;
signed long degrees = strtol(nmea, &amp;end, 10);

Now *end should contain a the period. If not, then the fractional part is missing and you can just normalise the degrees to the corresponding unit, in case of 'milli-minutes' with 60000.

With fraction available we now can go on:

nmea = end + 1; // assuming we&#39;re in a function thus have a copy
// of the pointer anyway
signed long milliMinutes = strtol(nmea, &amp;end, 10);

If trailing zeros are guaranteed to be included then end - nmea should now be equal to 5. If it is not – well, you need to extend:

ptrdiff_t length = end - nmea;
for(; length &lt; 5; ++length) // or whatever else desired precision
{
milliMinutes *= 10;
}

This is fine even if the case appears that you have too many digits (you'd have chosen a too small precision then). To cover that case as well, you instead need to divide - and we might remember the last digit dropped to determine rounding:

int digit = 0;
for(; length &gt; 5; --length)
{
digit = milliMinutes % 10;
milliMinutes /= 10;
}
milliMinutes += digit &gt;= 5;

Actually no need for surrounding ifs, the condition within the loop covers the respective inverse cases. Now finally you can combine, but you need to consider that signs of the two numbers might differ:

if(degrees &lt; 0)
{
milliMinutes = -milliMinutes;
}
milliMinutes += degrees + 60000;

or, if you prefer branchless:

milliMinutes
= degrees * 60000
+ milliMinutes * (degrees &gt;= 0)
- milliMinutes * (degrees &lt; 0);

Now you have a single integral value and none of the issues with floating point - perfect for internal handling.

The sole disadvantage: When outputting to the user by whatever means you then need to convert the value again to NMEA format; that's not much of an issue, though:

printf(&quot;%l.%.5l&quot;, milliMinutes / 10000, milliMinutes % 10000);

Of course you can adjust this to other precisions as well Is there a good way of converting an uint8_t array into a single float value that doesn't involve a loop?

Side note: All code entirely untested, if you find a bug, please fix yourself...

huangapple
  • 本文由 发表于 2023年5月26日 16:12:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76338897.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定