24

我正在解析固定 NMEA 句子中的 GPS 状态条目,其中地理分钟的一小部分总是在句点之后出现。但是,在语言环境将逗号定义为小数分隔符的系统上,atof函数会忽略句点和整数部分。

处理这个问题的最佳方法是什么?存储在字符数组中的长/纬度字符串,如果重要的话。

示例代码:

m_longitude = atof((char *)pField); 

在哪里

pField[] = "01000.3897"; 

跨平台项目,针对 Windows XP 和 CE 编译。

对解决方案的评论:

接受的答案更优雅,但这个答案(和评论)也值得作为快速修复来了解

4

8 回答 8

19

你总是可以使用(模错误检查):

#include <sstream>
...

float longitude = 0.0f;
std::istringstream istr(pField);

istr >> longitude;

The standard iostreams use the global locale by default (which in turn should be initialized to the classic (US) locale). Thus the above should work in general unless someone previously has changed the global locale to something else, even if you're running on a non-english platform. To be absolutely sure that the desired locale is used, create a specific locale and "imbue" the stream with that locale before reading from it:

#include <sstream>
#include <locale>

...
float longitude = 0.0f;
std::istringstream istr(pField);

istr.imbue(std::locale("C"));
istr >> longitude;

As a side note, I've usually used regular expressions to validate NMEA fields, extract the different parts of the field as captures, and then convert the different parts using the above method. The portion before the decimal point in an NMEA longitude field actually is formatted as "DDDMM.mmm.." where DDD correspond to degrees, MM.mmm to minutes (but I guess you already knew that).

于 2009-08-26T10:59:15.987 回答
7

我曾经做过的一个讨厌的解决方案是sprintf()0.0f 并从输出中获取第二个字符。然后在输入字符串中替换 '.' 由那个角色。这解决了逗号大小写的问题,但如果语言环境定义了其他小数分隔符,也可以使用。

于 2009-08-26T09:47:56.207 回答
7

This question is old, but in the meantime in C++ we got a "locale-independent" atof:

std::from_chars (with its sibling std::to_chars), added in c++17, provide locale-independent float scanning (and formatting). They are located in header <charconv>.

You can read more about them here:

https://en.cppreference.com/w/cpp/utility/from_chars

https://en.cppreference.com/w/cpp/utility/to_chars

I recomment Stephan T. Lavavej wonderful talk about these two tools, here's the link to the part where he talks about using std::from_chars: https://youtu.be/4P_kbF0EbZM?t=1367

And a short example by me:

#include <charconv>
#include <iostream>
#include <system_error>

int main()
{
    char buffer[16] { "123.45678" };
    float result;
    auto [p, ec] = std::from_chars(std::begin(buffer), std::end(buffer), result);
    if(ec == std::errc{})
        std::cout << result;
}

Unfortunately, as for today (05.06.2020) only MSVC supports these functions with floating types. Implementing them efficiently turned out to be a big problem.

@edit (27.04.2021) libstdc++ released today with stable GCC 11.1 adds support for floating-type <charconv>. However, this implementation seems to be not standard-compliant - it needs to copy the text into another buffer and calls strto(f/d/ld) with default C locale and set Floating Environment, taking error from errno. In extremely weird cases it can allocate, throw and catch exceptions underneath. You can find the implementation here: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B17/floating_from_chars.cc#L304

于 2020-06-05T17:21:25.570 回答
2

为什么您不能在 atof 之前执行setlocale "C" 并在之后恢复语言环境?也许我误解了这个问题......

于 2009-08-26T09:50:58.790 回答
0

您可以遍历数组中的所有字符并将任何非数字与.字符交换,只要坐标采用number-single_delimiter_character_-number格式,这应该可以工作。

于 2009-08-26T09:53:01.190 回答
0

Do you really need to get locale behavior for numerics? If not

setlocale(LC_ALL|~LC_NUMERIC, "");

or the equivalent use of std::locale constructor.

于 2009-08-26T11:22:42.663 回答
0

Some of the solutions above did not seem to work, so I propose this as a perfectly failproof solution. Just copy-paste this function and use it instead.

float stor(const char* str) {
    float result = 0;
    float sign = *str == '-' ? str++, -1 : 1;
    while (*str >= '0' && *str <= '9') {
        result *= 10;
        result += *str - '0';
        str++;
    }
    if (*str == ',' || *str == '.') {
        str++;
        float multiplier = 0.1;
        while (*str >= '0' && *str <= '9') {
            result += (*str - '0') * multiplier;
            multiplier /= 10;
            str++;
        }
    }
    result *= sign;
    if (*str == 'e' || *str == 'E') {
        str++;
        float powerer = *str == '-'? str++, 0.1 : 10;
        float power = 0;
        while (*str >= '0' && *str <= '9') {
            power *= 10;
            power += *str - '0';
            str++;
        }
        result *= pow(powerer, power);
    }
    return result;
}
于 2017-06-24T21:44:39.837 回答
0

I believe the simplest answer to this specific question would be to use the version of atof() which takes a C locale parameter:

_locale_t plocale = _create_locale( LC_ALL, "C" );

double result = _atof_l( "01000.3897", plocale );

_free_locale( plocale );

This allows you to not mess with streams, or the global locale, or with manipulating the string, at all. Just create the desired locale object to do all your processing with and then free it when you are finished.

于 2017-07-13T20:43:12.787 回答