Создание собственной функции для преобразования из кодированного числа строки в плавающее на языке Си - PullRequest
1 голос
/ 01 ноября 2019

Я пытаюсь сделать простую функцию для преобразования числа, кодифицированного в строковой форме, в число с плавающей запятой, используя функцию ниже (также прилагается), без , используя функцию strtof.

Я получаю неправильные значения, кто-нибудь может решить эту проблему? Почему это дает мне неправильные значения?

Например, если входная строка "123456789.123", функция вернет значение с плавающей запятой 123456789.123...

Im, используя DevC ++

if string == "12345678"
output = 12345678.000000 (CORRECT)

if string == "-12345678"
output = -12345678.000000 (CORRECT)

if string == "123456789"
output = 123456792.000000 (INCORRECT)

if string == "-123456789"
output = -123456792.000000 (INCORRECT)

if string == "1000.1"
output = 999.799988 (INCORRECT)

if string == "-1000.1"
-output = -999.799988 (INCORRECT)

Пока мой код:

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <math.h>


float StringToFloat (uint8_t *var);


int main()
{
    uint8_t string[64] = "-1000.1";

    float value = StringToFloat (string);

    printf("%f", value);

    return 0;
}



float StringToFloat (uint8_t *var)
{
    float multiplier;
    float result = 0;
    bool negative_num = false;
    bool found_comma_or_dot = false;
    uint16_t numbers_before_dot = 0;
    uint16_t numbers_after_dot = 0;
    uint16_t i = 0;

    while (*(var+i) != 0) 
    {
        if (*(var+i) == '-') { negative_num = true; }
        else if (*(var+i) == '.' || *(var+i) == ',') { found_comma_or_dot = true; }
        else 
        {
            if (found_comma_or_dot == false) { numbers_before_dot++; }
            if (found_comma_or_dot == true)  { numbers_after_dot++; }   
        }

        i++;
    }

    multiplier = pow (10, numbers_before_dot-1);


    while (*var != 0) 
    {
        if (*var == '-') { var++; }

        if (numbers_before_dot > 0) 
        {
            numbers_before_dot--;
            result += ( (*var) - 0x30) * (multiplier);
            multiplier /= 10;
        }


        else if (numbers_after_dot > 0) 
        {
            numbers_after_dot--;    
            result += ( (*var) - 0x30) * (multiplier);
            multiplier /= 10;
        }

        var++;
    }

    if (negative_num == true) 
    {
        result *= (-1);
    }

    return result;
}

Ответы [ 2 ]

0 голосов
/ 01 ноября 2019

Проблема в том, что float не имеет достаточной точности для представления числа 123456789. У нас есть 123456789 ≥ 2 ^ 26;в этом диапазоне число с плавающей запятой может представлять собой только числа, кратные 8.

Ошибка для чисел с десятичной точкой вызвана тем, что вы обрабатываете десятичную точку как дополнительную цифру, а не игнорируете ее. Так как код ASCII для "."на два меньше, чем код для «0», что «.»трактуется как цифра "-2".

PS. Использование 0x30 вместо '0' - очень плохой стиль.

PS. Используйте double вместо float, если у вас нет веских причин, по которым вы можете четко объяснить, почему вы этого не сделаете.

0 голосов
/ 01 ноября 2019

Проблема в точности. A float просто не очень точный. Чтобы пройти тесты, измените значение с float на double. Это можно увидеть, если вы запустите этот код:

float t = 123456789;
printf("%f\n", t);

Ваш код слишком сложен. Вот гораздо более удобное решение.

double StringToFloat(uint8_t *var)
{
        // First check if negative. If it is, just step one step forward
        // and treat the rest as a positive number. But remember the sign.
        double sign = 1; 
        if(*var=='-') {
                sign = -1;
                var++;
        }

        // Read until either the string terminates or we hit a dot
        uint32_t integer_part = 0;
        while(*var != 0) {
                if(*var == '.' || *var == ',') {
                        var++;
                        break;
                }

                integer_part = 10*integer_part + (*var - '0');
                var++;
        }

        // If we hit the string terminator in previous loop, we will do so
        // in the beginning of this loop too. If you think it makes things
        // clearer, you can add the boolean found_comma_or_dot to explicitly
        // skip this loop.
        uint32_t decimal_part = 0;
        uint32_t decimal_size = 0;
        while(*var != 0) {
                decimal_part = 10*decimal_part + (*var - '0');
                var++;
                decimal_size++;
        }

        return sign * (integer_part + decimal_part/pow(10, decimal_size));
}

Обратите внимание, что я изменил uint16_t на uint32_t, потому что я использую их по-другому. Если этот вариант не подходит для вас, вы можете изменить их на плавающий тип, но это может привести к потере точности.

Существуют причины для использования uint16_t и float, но выприходится жить с ограничениями. Так оно и есть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...