Как преобразовать строку в int64_t?

Как преобразовать параметр программы из argv в int64_t? atoi() подходит только для 32-битных целых чисел.


person pmichna    schedule 08.06.2013    source источник
comment
На платформе, где int64_t совпадает с long, быстрый хакерский способ — просто использовать atol().   -  person Oliver Charlesworth    schedule 08.06.2013
comment
Использование sscanf с соответствующим спецификатором для int64_t может обеспечить независимый от платформы метод.   -  person chux - Reinstate Monica    schedule 08.06.2013
comment
Немного педантичный момент: вы спрашиваете не о том, как преобразовать char * в int64_t (если вы этого хотите, ответом будет простое приведение), а вместо этого о том, как преобразовать строку, на которую указывает char *, которая представляет число в некотором ориентированное на человека текстовое соглашение, такое как десятичная строка, до int64_t.   -  person R.. GitHub STOP HELPING ICE    schedule 09.06.2013


Ответы (7)


Попытка соответствия C99.

[править] нанял @R. исправление

// Note: Typical values of SCNd64 include "lld" and "ld".
#include <inttypes.h>
#include <stdio.h>

int64_t S64(const char *s) {
  int64_t i;
  char c ;
  int scanned = sscanf(s, "%" SCNd64 "%c", &i, &c);
  if (scanned == 1) return i;
  if (scanned > 1) {
    // TBD about extra data found
    return i;
    }
  // TBD failed to scan;  
  return 0;  
}

int main(int argc, char *argv[]) {
  if (argc > 1) {
    int64_t i = S64(argv[1]);
    printf("%" SCNd64 "\n", i);
  }
  return 0;
}
person chux - Reinstate Monica    schedule 08.06.2013
comment
+1 за умную мысль использовать sscanf, но вам нужен именно SCNd64, а не PRId64. Мне также неясно, должен ли sscanf вести себя хорошо при переполнении. - person R.. GitHub STOP HELPING ICE; 09.06.2013
comment
@R.. Я думаю, что семейство scanf() предназначено для работы как семейство strtol() функций. В последнем случае ошибки диапазона приводят к тому, что ERANGE сохраняется в errno. (проект 7.8.2.3.3 C11). Таким образом, можно было бы включить тест errno. - person chux - Reinstate Monica; 09.06.2013
comment
@chux Ты уверен? Согласно это SCNd64 для scanf и PRId64 для printf . - person pmichna; 11.06.2013
comment
@chux: я не могу найти нигде, чтобы scanf вел себя так. Спецификация для scanf даже не устанавливает каких-либо требований к тому, как хранится определяемое значение. Он просто указывает ожидаемый формат ввода и требуемый тип вывода, но ничего не говорит о процедуре преобразования. Это кажется недосмотром, но оставляет много неясностей в отношении обработки ошибок... - person R.. GitHub STOP HELPING ICE; 11.06.2013
comment
@R.. Мое предложение о том, что семейство scanf() должно работать как strtol(), исходит из C11 Draft 7.21.6.2.12: d Соответствует необязательному десятичному целому числу со знаком, формат которого совпадает с ожидаемым для последовательности субъектов функции strtol с значение 10 для базового аргумента … . Мой компилятор Eclipse C Indigo Service Release 1 устанавливает errno в ERANGE через scanf(). Так что по крайней мере 1 компилятор делает это. Хотя strtol() устанавливает errno, я, как и вы, вижу неоднозначность в отношении scanf() обработки ошибок. Может другие знают? - person chux - Reinstate Monica; 11.06.2013
comment
@chux: процитированный вами текст описывает входные последовательности, которым соответствует спецификатор преобразования, но не способ его преобразования. Хотя цель, по-видимому, состоит в том, чтобы получить значение, как если бы совпадающая последовательность была преобразована с помощью strtol, это явно не указано, и на самом деле не существует варианта strtol, соответствующего преобразованиям меньше long, таким как %d или %hd. - person R.. GitHub STOP HELPING ICE; 11.06.2013
comment
Так вот, если бы %d конвертировалось как бы по strtol, возможно, переполнения long не произошло бы, но результат мог бы не уместиться в int, и в этом случае вообще не понятно, что должно произойти. (Зажим, как strtol? Преобразование, определяемое реализацией, как в выражениях C? Ошибка?) - person R.. GitHub STOP HELPING ICE; 11.06.2013
comment
ISO/IEC 9899:2011 §7.21.6.2 Функция fscanf ¶10 … Если подавление присваивания не указано *, результат преобразования помещается в объект, на который указывает первый аргумент, следующий за аргументом формата, который еще не получил результат преобразования. Если этот объект не имеет соответствующего типа или если результат преобразования не может быть представлен в объекте, поведение не определено. Таким образом, scanf() имеет UB при переполнении — также указано в Приложении J.2 Неопределенное поведение. - person Jonathan Leffler; 01.09.2017

Есть несколько способов сделать это:

  strtoll(str, NULL, 10);

Это соответствует стандарту POSIX C99.

вы также можете использовать strtoimax; который имеет следующий прототип:

 strtoimax(const char *str, char **endptr, int base);

Это хорошо, потому что всегда будет работать с локальным intmax_t... Это C99 и вам нужно включить <inttypes.h>

person Ahmed Masud    schedule 08.06.2013
comment
@OliCharlesworth да, вы правы, но приведение здесь как подсказка, чтобы напомнить, что, если OP хочет его проанализировать, они могут заменить его указателем на строку, вот и все. - person Ahmed Masud; 08.06.2013
comment
@AhmedMasud Вот для чего нужна документация. Или, может быть, комментарий. Пожалуйста, не добавляйте лишние приведения, они избыточны, могут даже скрыть ошибки скрытия и, самое главное, ухудшают читаемость. - person ; 08.06.2013
comment
@Ahmed: Ах, достаточно честно. Обратите внимание, что также, вероятно, стоит упомянуть, что этот подход предполагает, что int64_t совпадает с long long. - person Oliver Charlesworth; 08.06.2013
comment
@OliCharlesworth: это не предполагает, что они одинаковы, просто long long достаточно велико, чтобы хранить любое значение int64_t, что почти верно, но не совсем. Смотрите мой ответ. - person R.. GitHub STOP HELPING ICE; 08.06.2013
comment
Когда результат находится в диапазоне int64_t, этот метод подходит. Когда результат выходит за пределы диапазона int64_t, этот подход требует неопубликованной дополнительной работы для переносимого обнаружения таких проблем. - person chux - Reinstate Monica; 20.05.2016

strtoll преобразует его в long long, который обычно является 64-битным целым числом.

person Kninnug    schedule 08.06.2013

Сделать это на 100% переносимым немного сложно. long long должно быть не менее 64 бит, но не обязательно должно быть дополнением до двух, поэтому оно может не представлять -0x7fffffffffffffff-1, и, таким образом, использование strtoll может привести к нарушению углового регистра. Та же проблема относится и к strtoimax. Вместо этого вы можете использовать начальный пробел (если вы хотите разрешить начальный пробел) и сначала проверить знак, а затем использовать strtoull или strtoumax, любой из которых требуется для поддержки значений до полного положительного диапазона int64_t. Затем вы можете применить знак:

unsigned long long x = strtoull(s, 0, 0);
if (x > INT64_MAX || ...) goto error;
int64_t y = negative ? -(x-1)-1 : x;

Эта логика написана, чтобы избежать всех случаев переполнения.

person R.. GitHub STOP HELPING ICE    schedule 08.06.2013
comment
Но, честно говоря, я сомневаюсь, что int64_t определено в какой-либо системе с представлением со знаком, отличным от дополнения до двух. Я также думаю, что это одна из основных причин, по которой целые типы точной ширины являются необязательными. - person ouah; 08.06.2013
comment
Я думал, что это требуется POSIX, но на самом деле POSIX требует только 8-, 16- и 32-битных типов точного размера. int64_t и uint64_t являются необязательными в POSIX. Так что я согласен, что int64_t вряд ли будет существовать в системах, где long или long long не подходят. - person R.. GitHub STOP HELPING ICE; 09.06.2013
comment
Неясно, как написана логика, чтобы избежать всех случаев переполнения. -(x-1)-1 или -x работают так же, как x — широкий беззнаковый тип. - person chux - Reinstate Monica; 20.05.2016
comment
@chux: кажется, я имел в виду -((int64_t)x-1)-1. Это имеет больше смысла? - person R.. GitHub STOP HELPING ICE; 20.05.2016
comment
-((int64_t)x-1)-1 имеет больше смысла, но (int64_t)x по-прежнему означает целочисленное переполнение со знаком. Возможно -1-((int64_t)-(x+1))? - person chux - Reinstate Monica; 20.05.2016
comment
@chux: нет; x+1 может быть больше, чем INT64_MAX, или может быть нулевым, и в любом случае -(x+1) равно x+1 и либо дает неправильный результат, либо преобразование, определяемое реализацией, при приведении к int64_t. С другой стороны, (int64_t)x определено корректно, потому что приведенная выше строка только что проверила, что x находится в диапазоне int64_t. - person R.. GitHub STOP HELPING ICE; 21.05.2016
comment
Однако похоже, что в моем коде отсутствует логика для правильной обработки случая INT64_MIN... - person R.. GitHub STOP HELPING ICE; 21.05.2016
comment
x+1 не больше, чем INT64_MAX, поскольку я, как и вы в своем предыдущем комментарии, имел в виду левую половину : (когда negative истинно - (x<0)) OTOH, возможно, мне не хватает условия, когда установлено negative. IAC, приятно работать с вами. - person chux - Reinstate Monica; 21.05.2016
comment
@chux: Цель состоит в том, чтобы negative устанавливалось в зависимости от того, прочитали ли вы символ '-' перед числом. x<0 всегда ложно, поскольку имеет беззнаковый тип. - person R.. GitHub STOP HELPING ICE; 21.05.2016
comment
Чем больше я смотрю на это, тем больше я думаю, что мой ответ просто имеет серьезные недостатки, которые делают его не лучше, чем использование strtoll, а, вероятно, хуже. - person R.. GitHub STOP HELPING ICE; 21.05.2016

Пользователи, пришедшие из веб-поиска, также должны учитывать std::stoll.

Это не совсем эффективно отвечает на этот исходный вопрос для const char*, но у многих пользователей все равно будет std::string. Если вас не волнует эффективность, вы должны получить неявное преобразование (на основе определяемого пользователем преобразования с использованием конструктора std::string с одним аргументом) в std::string, даже если у вас есть const char*.

Это проще, чем std::strtoll, для которого всегда требуется 3 аргумента.

Он должен выдать, если ввод не является числом, но см. комментарии.

person davidvandebunte    schedule 12.12.2018
comment
Я думаю, что это лучший ответ для С++. - person Kyle; 16.07.2019

Это сработало для меня с другим типом int64, и мне нравится чистый стиль C++:

std::istringstream iss(argv[i]);
int64_t i64;
iss >> i64;

Вы можете получить ошибку компиляции: operator‹‹... не определен.

И я не знаю, что произойдет, если argv[i] будет содержать "HALLO".

person Fritz Pom    schedule 14.09.2018
comment
Если преобразование завершится неудачно (например, с HALLO), вы получите 0. Но также будет установлен бит ошибки, поэтому вам следует вызвать iss.fail(), чтобы проверить это. - person Zitrax; 21.10.2018

Как преобразовать строку в int64_t?

Простейший

#include <stdlib.h>
int64_t value = atoll(some_string);  // lacks error checking.  UB on overflow

Лучше

long long v = strtoll(s, NULL, 0);  // No reported errors, well defined on overflow

Надежный: создайте вспомогательную функцию для обнаружения всех проблем.

#include <stdbool.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>

// Return error flag
bool my_strtoi64(int64_t *value, const char *s) {
 // Maybe add a s==NULL, value==NULL checks.

  char *endptr;
  errno = 0;
  long long v = strtoll(s, &endptr, 0);

  // Optional code for future growth of `long long`
  #if LLONG_MIN < INT64_MIN || LLONG_MAX > INT64_MAX
  if (v < INT64_MIN) {
    v = INT64_MIN;
    errno = ERANGE;
  } else if (v > INT64_MAX) {
    v = INT64_MAX;
    errno = ERANGE;
  #endif

  *value = (int64_t) v;

  if (s == endptr) { // No conversion, *v is 0
    return true;
  }
  if (errno == ERANGE) { // Out of range
    return true;
  }
  if (errno) { // Additional implementations specific errors
    return true;
  }
  while (isspace(*(unsigned char* )endptr)) { // skip trail white-space
    endptr++;
  }
  if (*endptr) { // Non-numeric trailing text
    return true;
  }
  return false; // no error
}
person chux - Reinstate Monica    schedule 04.02.2021