В языке для написания критичного к ошибкам кода нужен особый подход к целочисленным типам, позволяющий уменьшить вероятность переполнения, а потому способствующий правильности программ.
Целочисленные типы должны быть разделены на две группы:- Ограниченное число предопределённых типов, подходящих для большинства задач, и к которым применимы встроенные арифметические операции: +, -, *, /, %.
- Типы из разделов, для работы с которыми необходимо использовать функции из тех же разделов с соответствующими названиями: add, sub, mul, div, mod и т.д.
Разделение сделает более удобным, а потому и более желанным использование типов, для которых риск совершения ошибки ниже, оставив возможность использовать более редкие особые случаи, которые будут применяться только в тех задачах, где они нужны.
Предопределённые типы
В первую группу должны войти:Название | Диапазон допустимых значений |
---|---|
byte | 0 .. (28 - 1) |
int | -(231 - 1) .. (231 - 1) |
longint | -(263 - 1) .. (263 - 1) |
Тип byte как особая разновидность целочисленных типов нужен в первую очередь для обработки блоков данных, например, для чтения и записи в файлы. Поэтому это единственный встроенный беззнаковый целочисленный тип, так как знаковость в таких задач только мешает. В арифметических операциях и при передаче в качестве фактического параметра-значения в вызове функции он эквивалентен типу int. Остальные же беззнаковые типы не включены в предопределение из-за того, что близость нижней границы повышает вероятность переполнения, и необходимость согласования знаковых и беззнаковых целых приводит к дополнительному источнику ошибок.
int и longint — знаковые, симметричные относительно 0 типы. Равенство по модулю максимального и минимального значений упрощает учёт правильных вычислений, в частности, гарантирует, что смена знака и значение по модулю всегда выполнимы без переполнения. Это важней желания вместить на одно допустимое значение больше, как это позволяют наиболее распространённые ныне платформы из-за использования дополнительного кода для кодирования отрицательных чисел. В большинстве случаев минимальное отрицательное даже не нужно, но непропорционально своему значению требует избыточного внимания в случае необходимости достижения полной правильности.
Главным целочисленным типом является int, так как он представляет хороший компромисс по предоставляемому диапазону чисел, достаточного для большинства задач, и возможностью эффективной обработки. В частности, количество элементов массива не может превышать максимального значения int. longint нужен как из-за того, что в современных условиях нередки ситуации, где диапазона 32-битных значений всё же не хватает, например, для кодирования времени в наносекундах или обработки больших файлов, так и потому что расширенный диапазон естественным образом возникает при вычислениях в полном диапазоне int.
Остальные целочисленные типы других диапазонов вынесены из ядра языка, так как потребность в них ниже основных, что, однако, не препятствует нередкому злоупотреблению ними и может приводить к лишней потребности сопряжения целочисленных типов разных диапазонов, что, в свою очередь, служит дополнительным источником ошибок.
В арифметических выражениях, в которых смешиваются операнды разных целочисленных типов, операнды меньшего типа рассматриваются как эквивалентные операнды наибольшего. Вычисления выражений для операндов в byte неявно происходят в диапазоне int, для int и longint в диапазоне longint. Контроль переполнения происходит как в выражениях, так и при сохранении значения. Если диапазон приёмника меньше диапазона типа выражения, требуется явное преобразование типа. Если такой же или больше, то не требуется, но контроль переполнения выполняется в любом случае. Такое правило позволяет уменьшить вероятность переполнений и не требует избыточных явных приведений типа.
Например, следующие операции, которые во многих других языках могут содержать ошибки переполнения при определённых значениях переменных, в защитном языке оказываются всегда правильны:
Защитный язык | Java |
---|---|
var (a,b,c,m int; d longint; f bool) a = max(int) / 2; b = a + 2; c = 300; m = min(int); f = a + b < c; // false d = a * b; // 1152921504606846975 c = b * 2 / 3; // 715827883 m = -m; // 2147483647 |
int a, b, c, m; long d; boolean f; a = Integer.MAX_VALUE / 2; b = a + 2; c = 300; m = Integer.MIN_VALUE; f = a + b < c; // true d = a * b; // -1 c = b * 2 / 3; // -715827882 m = -m; //-2147483648 |
Типы предопределённых разделов
Вторую группу целочисленных типов должны представить типы из состава специальных разделов, отличающихся знаковостью и разрядностью гарантированных диапазонов. Разделы должны предоставлять почти одинаковые наборы функций для работы с собственными целочисленными типами. Имена разделов можно вывести из синтаксического уравнения – имя = [u]int(8|16|32|64)
, где u
обозначает беззнаковость, а число, естественно — разрядность типа. Диапазоны допустимых значений определяются по формулам - [0 .. 2n-1]
для беззнаковых и [-2n-1 .. 2n-1-1]
для чисел со знаком, так как они должны быть закодированы через двоичное дополнение.
type (t) // сам целочисленный тип
const (min, max) // минимальное и максимальное значение
// Группа функций, которые трактуют переполнение как ошибку.
// Если при их выполнении не было явно получено состояние правильности,
// то в случае переполнения они завершают выполнение программы.
func add(addend1, addend2 t) (sum t);
func sub(minuend, subtrahend t) (difference t);
func mul(factor1, factor2 t) (product t);
func div(divident, divisor t) (ratio t);
func mod(divident, divisor t) (remainder t);
// Группа процедур, для которых переполнение сопровождается воображаемым
// отбросом старших разрядов.
proc mod_add(addend1, addend2, > sum t) (overflow bool);
proc mod_sub(minuend, subtrahend, > difference t) (overflow bool);
proc mod_mul(factor1, factor2, > product t) (overflow bool);
// Функции для преобразования к основным типам и обратно. Поскольку при
// этом может возникнуть переполнение, то функции также подразумевают
// явное взятие правильности, либо завершение работы.
func to_byte (value? t) (result byte);
func to_int (value? t) (result int);
func to_longint(value? t) (result longint);
func from_byte (value byte) (result t);
func from_int (value int) (result t);
func from_longint(value longint) (result t);
// Функции интерпретации стандартных типов как соответствующих их
// диапазону типов из разделов противоположной знаковости и наоборот.
// в разделе uint64:
func as_int64 (value t) (result int64.t);
func as_longint(value t) (result longint);
func as_t(value longint) (result t);
// в разделе uint32:
func as_int32 (value t) (result int32.t);
func as_int (value t) (result int);
func as_longint(value t) (result longint);
func as_t (value int) (result t);
// в разделе int8:
func as_byte (value t) (result byte);
func as_int (value t) (result int);
func as_longint(value t) (result longint);
func as_t (value byte) (result t);
Битовые операции для встроенных целочисленных типов не предусмотрены как неуместные. Вместо них вводятся:
- Встроенная функция для возведения в степень
func pow(v, n int) (res longint)
или отдельная операцияv**n
. Выбор будет сделан позднее. - Типы-множества, которые можно приводить к целочисленным и обратно и о которых подробней будет рассказано в отдельной заметке.
Дополнительные материалы:
Комментариев нет:
Отправить комментарий