- Почему «int» не работает правильно с «j», но long long работает нормально?
- 2 ответа
- Общаться с 64-битным long long в Visual C++
- Работа с очень длинными числами на C++
- Урок №31. Целочисленные типы данных: short, int и long
- Целочисленные типы данных
- Объявление целочисленных переменных
- Диапазоны значений и знак целочисленных типов данных
- Что используется по умолчанию: signed или unsigned?
- Переполнение
- Примеры переполнения
- Деление целочисленных переменных
- Комментариев: 23
Почему «int» не работает правильно с «j», но long long работает нормально?
Это мой код с int j :
Но он дает неправильный вывод, и вот мой код с long long j , который работает нормально:
В этом случае значение j значительно ниже 499993 , которое находится в диапазоне int , но, тем не менее, оно не работает. Почему это на самом деле происходит?
Вот ссылка на актуальную проблему. В случае, если вы хотите посмотреть.
2 ответа
Обратите внимание, что результатом ((4*i)-4)*(j) является целое число, поскольку оба типа i и j являются типами int. Правая часть переводится в длинную без знака только при добавлении ((4*i)-4)*(j) в sum . Но выражение ((4*i)-4)*(j) уже переполняет размер типа int для достаточно большого n перед повышением.
Однако если вы измените либо i , либо j на long без знака long, выражение ((4*i)-4)*(j) будет оценено как long unsigned long, безопасно в пределах размера.
В первом фрагменте кода в выражении
Оба операнда (4*i)-4) и (j) имеют тип int . Таким образом, тип выражения (общий тип операндов) также int . Но объект типа int недостаточно велик для хранения значения результата. Итак, здесь происходит переполнение.
Когда j объявлен как имеющий тип long long
Тогда общий тип выражения выше также long long . Это означает, что из-за обычного арифметического преобразования этот операнд (4*i)-4) также преобразуется в тип long long . И объект этого типа может хранить значение результата, предоставленное для введенных данных.
Вы можете проверить, какие максимальные значения могут храниться в объектах типа int и long long .
Источник
Общаться с 64-битным long long в Visual C++
Тип данных long long — это единственный целочисленный тип данных в Visual С++, который весит 8 байт и может использовать значения выше 2^31 — 1. -2^31. Если у вас 64-битная система, то тип long тоже будет 8-байтовым. А в 32-разрядной системе приходится довольствоваться только им. Этот тип данных получил права только в последних версиях С++, например, в С++ 6.0 вы его найдёте, но работать с ним там одно удовольствие.
Несмотря на то, что у long long есть закреплённые права и обязанности. Использовать его всё равно следует по-особенному. Для начала следует учесть, что простое присваивание ему значений не прокатит. Если вы хотите присвоить значение переменной типа long long, то не забудьте приставить к числу две буквы LL (которые видимо символизируют, что число «longlong» 64-битное).
long long temp = 1100LL;
Далее: про соответствие типов. Думаю, когда человек первый раз «берёт в руки» этот тип данных ему становится интересно, а как он соотносится с типом int?
Отношения у long long c int очень интересные. Если вы хотите складывать два числа int, зная, что результат выйдет за диапазон int, то вы можете использовать long long. Сложите два int присвоите long long и все будут счастливы.
int a1 = 2000000000;
int a2 = 2000000000;
long long b = a1 + a2;
b равен 400000000. И здесь проблем возникнуть не должно.
С умножением int’ов дела гораздо интереснее. Если вы просто возьмёте две переменные типа int и перемножите их между собой, зная, что результат выйдет за пределы int, то ничего у вас не получится. Переменная типа long long будет просто равна нулю. В итоге, чтобы умножать в Visual C++ числа типа int и получить long long следует проделать сложения через цикл, т.е. если мы хотим умножить число а на число b, то всё будет выглядеть так.
long long temp = 0;
for (int i = 0; i
Источник
Работа с очень длинными числами на C++
Недавно я решил написать свою собственную реализацию длинной арифметики для C++. Делал просто для себя, ибо эта тема мне кажется довольно интересной. Поставил перед собой следующие задачи:
Реализация должна быть шустрой. Я, к сожалению, пока не обладаю знаниями высшей математики, но пользоваться интернетом умею и постарался интегрировать в программу все, что только смог найти.
Реализация должна быть экономна к памяти. Понятное дело, что многопоточность и алгоритмы умножения вроде Карацубы подъедают память, но все-таки должен быть разумный предел.
Код должен быть относительно чистым, а то, как пользоваться классом — должно быть понятно и похоже на способы работы с обычными int-овыми числами.
Код должен содержать необходимый набор функций, чтобы используя его можно было спокойно проводить проверки на простоту и выполнять некоторые другие необходимые и популярные операции.
Сразу предупрежу, что статья не претендует на звание лучшей реализации длинных чисел, но все-же будет полезна тем, кто еще не реализовывал что-либо подобное.
Реализовывать буду знаковую целочисленную арифметику. Запаситесь провизией, поскольку рассказ будет долгий.
Для начала нам надо определиться, из чего будет состоять будущий проект. Выглядеть все будет примерно вот так:
Файл с инструкциями компиляции напишем сразу.
Во-первых, подключим потоки. Это понадобится, ибо некоторые попытки что-либо распараллелить тут будут задействованы.
Во-вторых, активируем флаг Werror дабы код был чище (флаг Werror отвечает за то, чтобы все предупреждения компилятора прерывали процесс компиляции, это помогает заранее устранять зоопарк в коде).
Файл с демонстрационным калькулятором пока пропустим, ибо сначала надо реализовать сам класс.
Делаем файл с заголовком класса:
Тут, надо обратить внимание на несколько моментов. Сами разряды числа будут храниться в векторе _digits с указанной системой счисления.
В переменной _sign будет храниться знак числа. True — если знак + (иными словами >= 0), false — если знак — (иными словами
Теперь, реализация. Обратите внимание, что алгоритм разделен на две части из-за рекурсии, а также на то, что функция факториала определена только для натуральных чисел.
Далее у нас файл с функцией подсчета НОД. Использовать мы будем алгоритм Евклида (см. Алгоритм Евклида). Обратите внимание, что результат этой операции всегда натурален, а также, что нельзя подсчитать НОД если оба числа нули.
Далее файл с функцией подсчета НОК. Зная НОД числа найти НОК не составляет труда. Обратите внимание, что нельзя найти НОК если одно из чисел ноль, а также, что НОК всегда натурально.
Теперь файл с функцией извлечения целого квадратного корня. Использоваться будет тот же бинарный поиск. Обратите внимание, что квадратного корня из отрицательного числа не существует.
Далее файл с функцией извлечения целого кубического корня. Целый кубический корень находит свое применение в ряде формлул, поэтому, его так же стоит написать. Извлечение целого кубического корня почти не отличается от извлечения целого квадратного корня, единственное, что стоит заметить, что кубический корень от отрицательного числа имеет решение, в отличие от квадратного.
Теперь напишем файл с генератором псевдослучайных чисел заданной длины. Выглядеть это будет вот так:
На этом реализация функций подошла к концу, осталось лишь накидать демонстрационную программу и замерить производительность.
Вот файл демонстрационной программы. Ничего объяснять, думаю, тут не надо — программа элементарна.
Все файлы готовы — теперь можно со спокойной душой компилировать. Компилируется на моем ноутбуке довольно шустро — пару секунд. Теперь, предлагаю замерить время выполнения самых «долгих» операций и сравнить ее с временем выполнения, скажем, в Python 3.9, где реализация длинных чисел уже выполнена из коробки. Обращаю внимание, что тесты производятся с учетом затрат на конвертацию типов и печать в консоль. Тесты будут проводиться на моем ноутбуке, вот информация о системе:
Debian GNU/Linux 11 Bullseye
Источник
Урок №31. Целочисленные типы данных: short, int и long
Обновл. 11 Сен 2021 |
На этом уроке мы рассмотрим целочисленные типы данных в языке С++, их диапазоны значений, операцию деления, а также переполнение (что это такое и примеры).
Целочисленные типы данных
Целочисленный тип данных — это тип, переменные которого могут содержать только целые числа (без дробной части, например: -2, -1, 0, 1, 2). В языке C++ есть 5 основных целочисленных типов, доступных для использования:
| Тип | Минимальный размер | |
| Символьный тип данных | char | 1 байт |
| Целочисленный тип данных | short | 2 байта |
| int | 2 байта (но чаще всего 4 байта) | |
| long | 4 байта | |
| long long | 8 байт |
Примечание: Тип char — это особый случай: он является как целочисленным, так и символьным типом данных. Об этом детально мы поговорим на одном из следующих уроков.
Основным различием между целочисленными типами, перечисленными выше, является их размер, чем он больше, тем больше значений сможет хранить переменная этого типа.
Объявление целочисленных переменных
Объявление происходит следующим образом:
В то время как полные названия short int , long int и long long int могут использоваться, их сокращенные версии (без int ) более предпочтительны для использования. К тому же постоянное добавление int затрудняет чтение кода (легко перепутать с именем переменной).
Диапазоны значений и знак целочисленных типов данных
Как вы уже знаете из предыдущего урока, переменная с n-ным количеством бит может хранить 2 n возможных значений. Но что это за значения? Это значения, которые находятся в диапазоне. Диапазон — это значения от и до, которые может хранить определенный тип данных. Диапазон целочисленной переменной определяется двумя факторами: её размером (измеряется в битах) и её знаком (который может быть signed или unsigned).
Целочисленный тип signed (со знаком) означает, что переменная может содержать как положительные, так и отрицательные числа. Чтобы объявить переменную как signed, используйте ключевое слово signed :
По умолчанию, ключевое слово signed пишется перед типом данных.
1-байтовая целочисленная переменная со знаком (signed) имеет диапазон значений от -128 до 127, т.е. любое значение от -128 до 127 (включительно) может храниться в ней безопасно.
В некоторых случаях мы можем заранее знать, что отрицательные числа в программе использоваться не будут. Это очень часто встречается при использовании переменных для хранения количества или размера чего-либо (например, ваш рост или вес не может быть отрицательным).
Целочисленный тип unsigned (без знака) может содержать только положительные числа. Чтобы объявить переменную как unsigned, используйте ключевое слово unsigned :
1-байтовая целочисленная переменная без знака (unsigned) имеет диапазон значений от 0 до 255.
Обратите внимание, объявление переменной как unsigned означает, что она не сможет содержать отрицательные числа (только положительные).
Теперь, когда вы поняли разницу между signed и unsigned, давайте рассмотрим диапазоны значений разных типов данных:
| Размер/Тип | Диапазон значений |
| 1 байт signed | от -128 до 127 |
| 1 байт unsigned | от 0 до 255 |
| 2 байта signed | от -32 768 до 32 767 |
| 2 байта unsigned | от 0 до 65 535 |
| 4 байта signed | от -2 147 483 648 до 2 147 483 647 |
| 4 байта unsigned | от 0 до 4 294 967 295 |
| 8 байтов signed | от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
| 8 байтов unsigned | от 0 до 18 446 744 073 709 551 615 |
Для математиков: Переменная signed с n-ным количеством бит имеет диапазон от -(2 n-1 ) до 2 n-1 -1. Переменная unsigned с n-ным количеством бит имеет диапазон от 0 до (2 n )-1.
Для нематематиков: Используем таблицу 🙂
Начинающие программисты иногда путаются между signed и unsigned переменными. Но есть простой способ запомнить их различия. Чем отличается отрицательное число от положительного? Правильно! Минусом спереди. Если минуса нет, значит число — положительное. Следовательно, целочисленный тип со знаком (signed) означает, что минус может присутствовать, т.е. числа могут быть как положительными, так и отрицательными. Целочисленный тип без знака (unsigned) означает, что минус спереди отсутствует, т.е. числа могут быть только положительными.
Что используется по умолчанию: signed или unsigned?
Так что же произойдет, если мы объявим переменную без указания signed или unsigned?
| Тип | По умолчанию | |
| Символьный тип данных | char | signed или unsigned (в большинстве случаев signed) |
| Целочисленный тип данных | short | signed |
| int | signed | |
| long | signed | |
| long long | signed |
Все целочисленные типы данных, кроме char, являются signed по умолчанию. Тип char может быть как signed, так и unsigned (но, обычно, signed).
В большинстве случаев ключевое слово signed не пишется (оно и так используется по умолчанию).
Программисты, как правило, избегают использования целочисленных типов unsigned, если в этом нет особой надобности, так как с переменными unsigned ошибок, по статистике, возникает больше, нежели с переменными signed.
Правило: Используйте целочисленные типы signed, вместо unsigned.
Переполнение
Вопрос: «Что произойдет, если мы попытаемся использовать значение, которое находится вне диапазона значений определенного типа данных?». Ответ: «Переполнение».
Переполнение (англ. «overflow») случается при потере бит из-за того, что переменной не было выделено достаточно памяти для их хранения.
На уроке №28 мы говорили о том, что данные хранятся в бинарном (двоичном) формате и каждый бит может иметь только 2 возможных значения ( 0 или 1 ). Вот как выглядит диапазон чисел от 0 до 15 в десятичной и двоичной системах:
| Десятичная система | Двоичная система |
| 0 | 0 |
| 1 | 1 |
| 2 | 10 |
| 3 | 11 |
| 4 | 100 |
| 5 | 101 |
| 6 | 110 |
| 7 | 111 |
| 8 | 1000 |
| 9 | 1001 |
| 10 | 1010 |
| 11 | 1011 |
| 12 | 1100 |
| 13 | 1101 |
| 14 | 1110 |
| 15 | 1111 |
Как вы можете видеть, чем больше число, тем больше ему требуется бит. Поскольку наши переменные имеют фиксированный размер, то на них накладываются ограничения на количество данных, которые они могут хранить.
Примеры переполнения
Рассмотрим переменную unsigned, которая состоит из 4 бит. Любое из двоичных чисел, перечисленных в таблице выше, поместится внутри этой переменной.
«Но что произойдет, если мы попытаемся присвоить значение, которое занимает больше 4 бит?». Правильно! Переполнение. Наша переменная будет хранить только 4 наименее значимых (те, что справа) бита, все остальные — потеряются.
Например, если мы попытаемся поместить число 21 в нашу 4-битную переменную:
| Десятичная система | Двоичная система |
| 21 | 10101 |
Число 21 занимает 5 бит (10101). 4 бита справа (0101) поместятся в переменную, а крайний левый бит (1) просто потеряется. Т.е. наша переменная будет содержать 0101, что равно 101 (нуль спереди не считается), а это уже число 5, а не 21.
Теперь рассмотрим пример в коде (тип short занимает 16 бит):
Результат выполнения программы:
x was: 65535
x is now: 0
Что случилось? Произошло переполнение, так как мы попытались присвоить переменной x значение больше, чем она способна в себе хранить.
Для тех, кто хочет знать больше: Число 65 535 в двоичной системе счисления представлено как 1111 1111 1111 1111. 65 535 — это наибольшее число, которое может хранить 2-байтовая (16 бит) целочисленная переменная без знака, так как это число использует все 16 бит. Когда мы добавляем 1, то получаем число 65 536. Число 65 536 представлено в двоичной системе как 1 0000 0000 0000 0000, и занимает 17 бит! Следовательно, самый главный бит (которым является 1) теряется, а все 16 бит справа — остаются. Комбинация 0000 0000 0000 0000 соответствует десятичному 0, что и является нашим результатом.
Аналогичным образом, мы получим переполнение, использовав число меньше минимального из диапазона допустимых значений:
Результат выполнения программы:
x was: 0
x is now: 65535
Переполнение приводит к потере информации, а это никогда не приветствуется. Если есть хоть малейшее подозрение или предположение, что значением переменной может быть число, которое находится вне диапазона допустимых значений используемого типа данных — используйте тип данных побольше!
Правило: Никогда не допускайте возникновения переполнения в ваших программах!
Деление целочисленных переменных
В языке C++ при делении двух целых чисел, где результатом является другое целое число, всё довольно предсказуемо:
Но что произойдет, если в результате деления двух целых чисел мы получим дробное число? Например:
В языке C++ при делении целых чисел результатом всегда будет другое целое число. А такие числа не могут иметь дробь (она просто отбрасывается, не округляется!).
Рассмотрим детально вышеприведенный пример: 8 / 5 = 1.6 . Но как мы уже знаем, при делении целых чисел результатом является другое целое число. Таким образом, дробная часть ( 0.6 ) значения отбрасывается и остается 1 .
Правило: Будьте осторожны при делении целых чисел, так как любая дробная часть всегда отбрасывается.
Поделиться в социальных сетях:
Урок №30. Размер типов данных
Комментариев: 23
Всем доброго времени суток. Появился такой вопрос: для объявления без знакового числа, для плюсов, обязательно писать unsigned int X, есть ли сокращенная форма по типу uint X?
Может проще для запоминания было сказать, что тип signed (со знаком) использует 1 (старший бит в байте для записи этого самого знака и для самого числа остается 7 бит (это в случае 1-го байта, для 2- байт 15 и т.д.) и в 7 битах можно записать число не больше чем 128.
К примеру 10000000 это отрицательный ноль. 🙂 Но такого не бывает.
хехе, а попробуйте к знаковому 2 байтному целоисчисленной переменной со значением 32767 прибавить 1, или так-же наоборот от -32768 отнять 1
Для того, чтоб числа имели дробь при делении целых чисел можно приписать ноль после точкой. Например : 8.0/5.0 = 1.6
Только это уже совсем другая история)
Достаточно поставить точку одному из выражений. Например: 8. / 5 или 8 / 5.
Остальное компилятор сам подставит)
Вообще, с «железным» правилом «Никогда не допускайте возникновения переполнения в ваших программах!» — сильно погорячились. Потому что очень часто переполнение как раз помогает создать более простой и быстрый код.
Например, нужно много раз увеличивать переменную на 1 и циклически прокручивать все значения от 0 до 255. Писать условие «если равно 255, то присвоить 0» — совсем не нужно, это произойдёт само при прибавлении 1 к 255, если используется 1-байтовая беззнаковая.
Другой очень частый пример: вычисление разности двух значений миллисекундного таймера, чтобы замерить период времени. 4-байтовая переменная с таким таймером переполняется каждые 49 суток. Если система работает непрерывно, то такое может случаться. Когда считаем разность (новое значение таймера минус старое) — возможен случай, когда новое значение уже переполнилось (снова пошло с нуля), а старое ещё нет (огромное число). Но когда вычисляется разность, тут снова произойдёт переполнение (из-за того, что получилось отрицательное значение), и эти два переполнения оказывают взаимно компенсирующее действие, как будто их не было вообще. И разность всё равно будет верной. И не надо городить никаких хитрых алгоритмов.
В некоторых источниках встречал, что в связи со знаковостью, появляются два варианта нуля: +0 и -0. Почему-то тут этот вопрос не затронут. Ему перестали предавать значение в сообществе?
Скорее всего это какой-то очень древний подход. Никогда не слышал подобного в универе.
Потому что это относится к числам с плавающей точкой. У них отдельный бит хранит знак. В целочисленных типах такого нигде (или почти нигде) нет.
unsigned используется для экономии памяти, это же очевидно. Если знак действительно не нужен за счет дополнительно освобожденного бита, можно увеличить диапазон значений в 2 раза, что в некоторых случаях позволит использовать более «экономные» типы данных.
так если при делении дробная часть отбрасывается ,то как создать калькулятор?Если он не будут выводить дробные числа.Или ответ стоит присвоить к переменной которая будет иметь тип float?
Ну так нужно указывать другой тип переменной(не целое число). Тогда будет дробь.
Забавная история, почему этот урок так важен =)
В игре Civilization есть баг с механикой агрессии и миролюбия. Суть такова, что агрессивность цивилизации измерялась по шкале от 1 до 10. Девятки и десятки были у всяких Чингисханов, Монтесум и Сталиных, а у духовного пацифиста Махатмы Ганди была единичка. И ещё были модификаторы — строй «республика» уменьшает агрессивность на 1, «демократия» — на 2. Соответственно, сразу же, как только индусы открывали Демократию, у Ганди становилась агрессивность −1.
А теперь внимание. Эта переменная была однобайтная и строго неотрицательная(unsigned), от 0 до 255. Соответственно, агрессивность Махатмы Ганди становилась равна 255 из 10. Поэтому, построив у себя демократию, Ганди двигался рассудком, клепал ядрёные бомбы и умножал всех на ноль.
Действительно хороший пример 🙂 С unsigned нужно быть аккуратным.
Источник