конвертирование double в int (что-то не так с округлением)

Модератор: Модераторы разделов

Ответить
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

конвертирование double в int

Сообщение Stauffenberg »

Всем привет.

Наверняка кто-то сталкивался с проблемой конвертирования double в int, когда где-то в 10% случаев получается не целое положительное число (как должно быть), а отрицательное, притом большое.

Код: Выделить всё

int diff = ((int)difftime(t_of_sys, t_of_file))/60;

difftime возвращает double.
diff получается почти всегда правильно. Разница во времени у меня не больше 5 минут. Но иногда diff становится равным -2405 или еще больше.
Мне интересно - как лучше всего решить проблему. Наверняка есть чистое 100% правильное решение.

Благодарю за ответы.
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
Аватара пользователя
Bizdelnick
Модератор
Сообщения: 20797
Статус: nulla salus bello
ОС: Debian GNU/Linux

Re: конвертирование double в int

Сообщение Bizdelnick »

100% правильное решение — использовать double, а не int. По крайней мере до тех пор, пока Вы не привели пример кода, который не позволял бы этого делать.
Upd. А если проблема в том, что переполнения в данном случае происходить не может в принципе, то, вероятно, у Вас ошибка где-то выше по коду, и значение t_of_sys или t_of_file отличается от того, что Вы ожидаете.
Пишите правильно:
в консоли
вку́пе (с чем-либо)
в общем
вообще
в течение (часа)
новичок
нюанс
по умолчанию
приемлемо
проблема
пробовать
трафик
Спасибо сказали:
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

Re: конвертирование double в int

Сообщение Stauffenberg »

Код: Выделить всё

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

char *parse(char *LOGFILE, char *IP)
{
    FILE *fp;
    char *line = NULL;
    char *last = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen(LOGFILE, "r");
        if (fp == NULL){
            printf("I can't open %s\n", LOGFILE);
            exit(2);
        }
    while ((read = getline(&line, &len, fp)) != -1) {
        if (strstr(line, IP)) {
            size_t n = strxfrm(NULL, line, 0);
            last = malloc(n + 1);
            if(last)
                strxfrm(last, line, n);
        }
    }
    return last;
}


int main(int argc, char *argv[])
{
    struct tm info, the_system_time;
    time_t t_of_sys = time(NULL);
    memcpy(&the_system_time,localtime(&t_of_sys),sizeof(struct tm));
    memcpy(&info,localtime(&t_of_sys),sizeof(struct tm));

    char *last = parse("/var/log/maillog", argv[1]);
    if (!last) {
        last = parse("/var/log/maillog.1", argv[1]);
        if (!last) {
            printf("CRITICAL: no emails for a long time...\n");
            exit(2);
        }
    }
    char year[5];
    strncpy(year, last, 4);
    info.tm_year = atoi(year) - 1900;

    char mon[5];
    strncpy(mon, last+5, 2);
    info.tm_mon = atoi(mon) - 1;

    char day[5];
    strncpy(day, last+8, 2);
    info.tm_mday = atoi(day);

    char hour[5];
    strncpy(hour, last+11, 2);
    info.tm_hour = atoi(hour);

    char min[5];
    strncpy(min, last+14, 2);
    info.tm_min = atoi(min);

    char sec[5];
    strncpy(sec, last+16, 2);
    info.tm_sec = atoi(sec);

    time_t t_of_file = mktime(&info);
    int diff = (int)difftime(t_of_sys, t_of_file)/60;

    if (diff > atoi(argv[2])) {
        printf("CRITICAL: last email was reseived %d min ago.\n", diff);
        exit(2);
    }
    else
        printf("OK: last email was reseived %d min ago.\n", diff);
    return 0;
}
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
Аватара пользователя
Bizdelnick
Модератор
Сообщения: 20797
Статус: nulla salus bello
ОС: Debian GNU/Linux

Re: конвертирование double в int

Сообщение Bizdelnick »

Напрашивается предположение, что у Вас некорректно парсится время из лога. Если Вы всё же предполагаете, что дело в преобразовании, попробуйте всё-таки отказаться от него (strtod(3) в помощь).
Пишите правильно:
в консоли
вку́пе (с чем-либо)
в общем
вообще
в течение (часа)
новичок
нюанс
по умолчанию
приемлемо
проблема
пробовать
трафик
Спасибо сказали:
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

Re: конвертирование double в int

Сообщение Stauffenberg »

Не буду скрывать, был бы очень благодарен за что-то более конкретное (:
Есть на форуме C программисты - тут хитрый баг, но возможно его сразу же увидит тот, кто занимался парсингом строк в C?
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: конвертирование double в int

Сообщение NickLion »

1. getline выделяет буфер, который Вы не освобождаете. Нехорошо, нехорошо ;)
2. strncpy копирует null символ, если он встречается в указанном количестве символов, если же нет, то ничего не добавляет. Поэтому запись вида:

Код: Выделить всё

    char day[5];
    strncpy(day, last+8, 2);
    info.tm_mday = atoi(day);

Явно содержит ошибку, гарантировать содержимое day Вы не можете, там может быть ещё какие-то символы, которые будут восприняты как часть строки. Добавьте 0 в конец:

Код: Выделить всё

    char day[5];
    strncpy(day, last+8, 2);
    day[2] = 0;
    info.tm_mday = atoi(day);

Естественно так не только для day, а и для других переменных (с учётом количества символов).

3. Вы уверены, что дата всегда записана именно в этих позициях и никогда не смещена?
Спасибо сказали:
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

Re: конвертирование double в int

Сообщение Stauffenberg »

NickLion писал(а):
04.12.2015 00:37
1. getline выделяет буфер, который Вы не освобождаете. Нехорошо, нехорошо ;)

Да, это надо исправить (:

NickLion писал(а):
04.12.2015 00:37
2. strncpy копирует null символ, если он встречается в указанном количестве символов, если же нет, то ничего не добавляет. Поэтому запись вида:

Код: Выделить всё

    char day[5];
    strncpy(day, last+8, 2);
    info.tm_mday = atoi(day);

Явно содержит ошибку, гарантировать содержимое day Вы не можете, там может быть ещё какие-то символы, которые будут восприняты как часть строки. Добавьте 0 в конец:

Код: Выделить всё

    char day[5];
    strncpy(day, last+8, 2);
    day[2] = 0;
    info.tm_mday = atoi(day);

Естественно так не только для day, а и для других переменных (с учётом количества символов).

Да, я заметил, что у меня не получалось использовать один и тот же буфер несколько раз (выделять каждый раз новый указатель, это конечно детский сад), но по невнимательности, читая man, проглядел название функции и был уверен в terminating null byte:
The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to by dest.

Исправлю и завтра опробую снова. Большое спасибо!

Кстати, возможно в этом и проблема - какой-то из строк начинается с нуля (сегодняшний день, например, 03). Хотя в этом случае ошибка должна была бы происходить каждый раз.

NickLion писал(а):
04.12.2015 00:37
3. Вы уверены, что дата всегда записана именно в этих позициях и никогда не смещена?

Да, это точно. Формат один и тот же для любых "событий".
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: конвертирование double в int

Сообщение NickLion »

2. И ещё, меня смущает парсинг секунд — смещение относительно минут всего на 2, там нет разделителя?

3. Если такое постоянство, я бы на Вашем месте вообще избавился от копирований strncpy в буфер и вызовов atoi, как-то так:

Код: Выделить всё

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <time.h>

time_t parse_date(const char* line);

int main(int argc, const char *argv[])
{
    printf("%d\n", parse_date("2015-12-04 11:40:18 some other info"));
    return 0;
}

unsigned int extract_uint(const char* str, int count)
{
    unsigned int r = 0;
    int i;
    for (i = 0; i < count; i++) {
        r *= 10;
        r += str[i] - '0';
    }
    return r;
}

time_t parse_date(const char* line)
{
    struct tm info;
    time_t t_of_sys = time(NULL);
    memcpy(&info,localtime(&t_of_sys),sizeof(struct tm));
    info.tm_year = extract_uint(line + 0, 4) - 1900;
    info.tm_mon = extract_uint(line + 5, 2) - 1;
    info.tm_mday = extract_uint(line + 8, 2);
    info.tm_hour = extract_uint(line + 11, 2);
    info.tm_min = extract_uint(line + 14, 2);
    info.tm_sec = extract_uint(line + 17, 2);
    return mktime(&info);
}
Спасибо сказали:
Аватара пользователя
s.xbatob
Сообщения: 1139
ОС: Fedora

Re: конвертирование double в int

Сообщение s.xbatob »

Stauffenberg писал(а):
03.12.2015 17:58
Всем привет.

Наверняка кто-то сталкивался с проблемой конвертирования double в int, когда где-то в 10% случаев получается не целое положительное число (как должно быть), а отрицательное, притом большое.

Код: Выделить всё

int diff = ((int)difftime(t_of_sys, t_of_file))/60;

difftime возвращает double.
diff получается почти всегда правильно. Разница во времени у меня не больше 5 минут. Но иногда diff становится равным -2405 или еще больше.
Мне интересно - как лучше всего решить проблему. Наверняка есть чистое 100% правильное решение.

Благодарю за ответы.

time_t это 32-битное целое со знаком. Чтобы вычесть одно целое из другого, так извращаться не обязательно. ;)
А вот связываться с вещественной арифметикой без крайней необходимости не стоит: она работает не слишком адекватно.
Спасибо сказали:
Аватара пользователя
Bizdelnick
Модератор
Сообщения: 20797
Статус: nulla salus bello
ОС: Debian GNU/Linux

Re: конвертирование double в int

Сообщение Bizdelnick »

s.xbatob писал(а):
04.12.2015 18:27
time_t это 32-битное целое со знаком.

Это в каком стандарте такое написано?
Пишите правильно:
в консоли
вку́пе (с чем-либо)
в общем
вообще
в течение (часа)
новичок
нюанс
по умолчанию
приемлемо
проблема
пробовать
трафик
Спасибо сказали:
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

Re: конвертирование double в int

Сообщение Stauffenberg »

NickLion писал(а):
04.12.2015 00:37
2. strncpy копирует null символ, если он встречается в указанном количестве символов, если же нет, то ничего не добавляет. Поэтому запись вида:

Код: Выделить всё

    char day[5];
    strncpy(day, last+8, 2);
    info.tm_mday = atoi(day);

Явно содержит ошибку, гарантировать содержимое day Вы не можете, там может быть ещё какие-то символы, которые будут восприняты как часть строки. Добавьте 0 в конец:

Код: Выделить всё

    char day[5];
    strncpy(day, last+8, 2);
    day[2] = 0;
    info.tm_mday = atoi(day);

Естественно так не только для day, а и для других переменных (с учётом количества символов).

Да, проблема была в этом.
Теперь использую один char * указатель для года, месяца, дня и т.д. Первый раз инициализирую пятый элемент нулем, второй раз инициализирую третий элемент нулем (для всех остальных третий тоже должен быть нуль).


NickLion писал(а):
04.12.2015 13:21
2. И ещё, меня смущает парсинг секунд — смещение относительно минут всего на 2, там нет разделителя?

Вот такой формат:
2015-11-29T04:05:01.263739+01:00


NickLion писал(а):
04.12.2015 00:37
1. getline выделяет буфер, который Вы не освобождаете. Нехорошо, нехорошо ;)

Код: Выделить всё

char *parse(char *LOGFILE, char *IP)
{
    FILE *fp;
    char *line = NULL;
    char *last = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen(LOGFILE, "r");
        if (fp == NULL){
            printf("I can't open %s\n", LOGFILE);
            exit(2);
        }
    while ((read = getline(&line, &len, fp)) != -1) {
        if (strstr(line, IP)) {
            size_t n = strxfrm(NULL, line, 0);
            last = malloc(n + 1);
            if(last)
                strxfrm(last, line, n);
        }
    }
    free(line);
    return last;
}
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
Аватара пользователя
Bizdelnick
Модератор
Сообщения: 20797
Статус: nulla salus bello
ОС: Debian GNU/Linux

Re: конвертирование double в int

Сообщение Bizdelnick »

Stauffenberg писал(а):
04.12.2015 19:46
Теперь использую один char * указатель для года, месяца, дня и т.д. Первый раз инициализирую пятый элемент нулем, второй раз инициализирую третий элемент нулем (для всех остальных третий тоже должен быть нуль).

Предлагаю пойти дальше и расставить нули прямо в исходной строке, а strncpy() вообще не вызывать. Чего байты гонять туда-сюда?
Пишите правильно:
в консоли
вку́пе (с чем-либо)
в общем
вообще
в течение (часа)
новичок
нюанс
по умолчанию
приемлемо
проблема
пробовать
трафик
Спасибо сказали:
Аватара пользователя
s.xbatob
Сообщения: 1139
ОС: Fedora

Re: конвертирование double в int

Сообщение s.xbatob »

Bizdelnick писал(а):
04.12.2015 19:01
s.xbatob писал(а):
04.12.2015 18:27
time_t это 32-битное целое со знаком.

Это в каком стандарте такое написано?

Те, кому "важнее шашечки", вольны ходить по граблям и дальше. Равно как и те, кто надумают воспользоваться формальным правом изменить его размер или разрешение. Но стандарт (конкретно ISO 9899 всех версий) декларирует его арифметическим типом, стало быть он как операцию вычитания, так и преобразования в int, должен поддерживать напрямую.
Спасибо сказали:
yoshakar
Сообщения: 259
ОС: Debian Stretch

Re: конвертирование double в int

Сообщение yoshakar »

s.xbatob писал(а):
04.12.2015 21:53
стало быть он как операцию вычитания, так и преобразования в int, должен поддерживать напрямую
Это замечательно, но отсюда никак не следует, что time_t - это целое со знаком, и уж тем более что это 32-битное целое со знаком (особенно учитывая что int - это не 32-битное со знаком).
Спасибо сказали:
Аватара пользователя
s.xbatob
Сообщения: 1139
ОС: Fedora

Re: конвертирование double в int

Сообщение s.xbatob »

yoshakar писал(а):
04.12.2015 23:44
s.xbatob писал(а):
04.12.2015 21:53
стало быть он как операцию вычитания, так и преобразования в int, должен поддерживать напрямую
Это замечательно, но отсюда никак не следует, что time_t - это целое со знаком, и уж тем более что это 32-битное целое со знаком (особенно учитывая что int - это не 32-битное со знаком).

И где это не так? Последние 40 лет на это ещё никто не покушался, даже Micro$oft в Xenix, а вместо этого зачем-то придумывали всякие нестандартные struct timeval/timespec... У вас есть шанс стать первооткрывателем. ;)
Ну да, на 16-битных платформах int был 16-битным, однако на нынешних 64-битных он почему-то остаётся 32-битным. Похоже, повторяется история с char, который на самом деле байт (который в свою очередь размером исключительно 8 двоичных бит), хотя символы в этот байт уже давно не помещаются.
Спасибо сказали:
Аватара пользователя
/dev/random
Администратор
Сообщения: 5289
ОС: Gentoo

Re: конвертирование double в int

Сообщение /dev/random »

s.xbatob писал(а):
05.12.2015 13:43
yoshakar писал(а):
04.12.2015 23:44
s.xbatob писал(а):
04.12.2015 21:53
стало быть он как операцию вычитания, так и преобразования в int, должен поддерживать напрямую
Это замечательно, но отсюда никак не следует, что time_t - это целое со знаком, и уж тем более что это 32-битное целое со знаком (особенно учитывая что int - это не 32-битное со знаком).

И где это не так? Последние 40 лет на это ещё никто не покушался, даже Micro$oft в Xenix, а вместо этого зачем-то придумывали всякие нестандартные struct timeval/timespec... У вас есть шанс стать первооткрывателем. ;)
Ну да, на 16-битных платформах int был 16-битным, однако на нынешних 64-битных он почему-то остаётся 32-битным. Похоже, повторяется история с char, который на самом деле байт (который в свою очередь размером исключительно 8 двоичных бит), хотя символы в этот байт уже давно не помещаются.

Пример, где int не 32-битный, вы сами привели: 16-разрядные операционки. Есть и другие. Например, в некоторых вариантах солярки int 64-битный. Погуглите ILP64 и SILP64 для более полного списка операционок с 64-битным int.

А time_t даже в обычном 64-битном линуксе 64-битный, а не 32-битный, как вы написали.
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: конвертирование double в int

Сообщение NickLion »

Stauffenberg писал(а):
04.12.2015 19:46
NickLion писал(а):
04.12.2015 13:21
2. И ещё, меня смущает парсинг секунд — смещение относительно минут всего на 2, там нет разделителя?

Вот такой формат:
2015-11-29T04:05:01.263739+01:00

Тогда смещение у секунд 17, а не 16 — обратите внимание, если ещё не исправили. Ну, и подуймайте о таком варианте:

Код: Выделить всё

strptime(line, "%FT%T", &info);
Спасибо сказали:
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

Re: конвертирование double в int

Сообщение Stauffenberg »

Bizdelnick писал(а):
04.12.2015 19:53
Stauffenberg писал(а):
04.12.2015 19:46
Теперь использую один char * указатель для года, месяца, дня и т.д. Первый раз инициализирую пятый элемент нулем, второй раз инициализирую третий элемент нулем (для всех остальных третий тоже должен быть нуль).

Предлагаю пойти дальше и расставить нули прямо в исходной строке, а strncpy() вообще не вызывать. Чего байты гонять туда-сюда?

Да, неплохая идея. Правда, такая оптимизация сделает код менее понятным. Для автора это не проблема, но если код будет читать кто-то другой, он вряд ли будет в восторге.
"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)

Код: Выделить всё

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Version: %5.2f\n", VERSION);
        printf("USAGE: %s <IP_ADDRESS> <CRITICAL_TIME_PERIOD_IN_MIN>\n", argv[0]);
        exit(1);
    }

    struct tm info, the_system_time;
    time_t t_of_sys = time(NULL);
    memcpy(&the_system_time,localtime(&t_of_sys),sizeof(struct tm));
    memcpy(&info,localtime(&t_of_sys),sizeof(struct tm));

    char *last = parse("/var/log/maillog", argv[1]);

    /* If email (IP address of host) not found in
     * /var/log/maillog, thy to find it in /var/log/maillog.1
     * Sometimes we will start this script after few minutes
     * or secons of creating a new MTA logfile.
     *
     */
    if (!last) {
        last = parse("/var/log/maillog.1", argv[1]);
        if (!last) {
            printf("CRITICAL: no emails for a long time...\n");
            exit(2);
        }
    }

    last[4] = last[7] = last[10] = last[13] = last[16] = last[19] = 0;
    info.tm_year = atoi(last) - 1900;
    info.tm_mon  = atoi(last+5) - 1;
    info.tm_mday = atoi(last+8);
    info.tm_hour = atoi(last+11);
    info.tm_min  = atoi(last+14);
    info.tm_sec  = atoi(last+17);

    time_t t_of_file = mktime(&info);
    int diff = (int)difftime(t_of_sys, t_of_file)/60;

    if (diff > atoi(argv[2])) {
        printf("CRITICAL: last email was reseived %d min ago.\n", diff);
        exit(2);
    }
    else
        printf("OK: last email was reseived %d min ago.\n", diff);

    return 0;
}
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
Аватара пользователя
Stauffenberg
Сообщения: 2042
Статус: ☮ PEACE ☮
ОС: открытая и свободная

Re: конвертирование double в int

Сообщение Stauffenberg »

NickLion писал(а):
05.12.2015 18:32
Ну, и подуймайте о таком варианте:

Код: Выделить всё

strptime(line, "%FT%T", &info);


Заменил

Код: Выделить всё

    last[4] = last[7] = last[10] = last[13] = last[16] = last[19] = 0;
    info.tm_year = atoi(last) - 1900;
    info.tm_mon  = atoi(last+5) - 1;
    info.tm_mday = atoi(last+8);
    info.tm_hour = atoi(last+11);
    info.tm_min  = atoi(last+14);
    info.tm_sec  = atoi(last+17);

на

Код: Выделить всё

strptime(last, "%Y-%m-%dT%H:%M:%S", &info);

Спасибо.

p.s. придумал себе задачку - попарсить строки в С. В итоге использую библиотечную функцию, которая делает это за меня (;
Labor omnia vincit

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” (Brian Kernighan)
Спасибо сказали:
Ответить