Pull to refresh

Comments 53

А еще бы хорошо иметь числа с фиксированной точкой. Может быть сейчас их и можно сделать на шаблонах и пользовательских литералах, не знаю. Для разных малых микроконтроллеров, где нет FPU, такие числа были бы весьма удобны.

Идею с INT_NAN поддерживаю.

Еще есть понятие обработки переполнения. Есть понятие режим насыщения, когда 255+1==255, а не 0, но не знаю в каких языках оно поддерживается. Зато в C# есть checked и unchecked. В общем, эти фундаментальные вещи хорошо бы иметь на уровне языка.

И еще конечно числа неограниченной длины на уровне числовых литералов (а не строковых, как делается во всяких GMP).

Их можно иметь на уровне языка только когда они есть на уровне железа. Иначе множество классной оптимизации уйдёт в труху. Мы же говорим о С++, весь смысл брать который для проекта это возможность ручной оптимизации.

Числа с фиксированной точкой на уровне железа - это обычные целые числа. Но вот удерживать в памяти и постоянно писать в комментариях, что "вот в этой переменной с такого-то разряда начинается дробная часть" - крайне неудобно.

Там где я видел их применение и сам пользовался, использовалась IQ либа от TI на их же контроллерах. Причем под целую часть все оставляли только знак. По сути при вычислениях они были всегда в диапазоне -1..+1. Ну а для перевода в/из реальных значений использовались коэффициенты масштабирования. вот эта штука сильно мозг ломала поначалу.

Как уже отметили, "на уровне железа" это просто целые числа. Но компилятор должен "помнить" что вот у этого целого числа два знака после запятой, у того - три.

Я сейчас работаю на платформе, где такие типы есть. Правда, реализованы они не на уровне компилятора, а на уровне неких "системных" объектов (тут вся система "объектная" - все есть объект, в т.ч. и все переменные на уровне системы представлены объектами каждый со своим типом и свойствами).

Правда, числа с фиксированной точкой не являются "обычными" целыми числами - формат хранения их отличается от обычного int. Существует два формата чисел с фиксированной точкой - packed и zoned:

Представление числа 21544 в различных форматах
Представление числа 21544 в различных форматах

Для формата zoned есть еще один "сахарок" - для положительных чисел внутреннее представление zoned совпадает со строковым - в кодовой таблице EBCDIC, используемой в данной системе, цифры 0..9 имеют коды 0xF0..0xF9 т.е. строка "21544" в памяти представлена как F2F1F5F4F4 что полностью совпадает с ее zoned представлением.

Для компилятора С есть расширение в виде макроса decimal():

/* this example demonstrates the use of the decimal type */

#include <decimal.h>

decimal(31,4) pd01 = 1234.5678d;
decimal(29,4) pd02 = 1234.5678d;

int main(void)
{
  /* The results are different in the next two statements */
  pd01 = pd01 + 1d;
  pd02 = pd02 + 1d;

  printf("pd01 = %D(31,4)\n", pd01);
  printf("pd02 = %D(29,4)\n", pd02);

  /* Warning: The decimal variable with size 31 should not be      */
  /*          used in arithmetic operation.                        */
  /*          In the above example: (31,4) + (1,0) ==> (31,3)      */
  /*                                (29,4) + (1,0) ==> (30,4)      */

  return(0);
}

В С++ определен шаблон _DecimalT<>

#include<bcd.h>

void main ()
{
  _DecimalT<4,0> d40 = __D("123");              // OK
  _DecimalT<6,0> d60 = __D(d40);                // Because no constructor
                                                // exists that can convert d40 to d60.
                                                // macro __D is needed to convert d40
                                                // into an intermediate type first.
                                                
  d60 = d40;                                    // OK. This is different from the
                                                // previous statement in which
                                                // the constructor was called.
                                                // In this case, the assignment
                                                // operator is called and the
                                                // compiler converts d40 into the
                                                // intermediate type automatically.
                                                
  _DecimalT<8,0> d80 = (_DecimalT<7,0>)1;       // OK
                                                // Type casting an int,not a decimal(n,p)
                                                
  _DecimalT<(9,0> d90;                          // OK
  
  d60 = (_DecimalT<7,0>)__D("12");              // OK
  
  d60 = (_DecimalT<4,0>)__D(d80);
  d60 = (_DecimalT<4,0>)__D(d80 + 1);           // In both cases, the resultant classes
                                                // of the expressions are _DecimalT<n,p>.
                                                // macro __D is needed to convert them
                                                // to an intermediate type first.
                                                
  d60 = (_DecimalT<4,0>)(d80 + (float)4.500);   // OK because the resultant type
                                                // is a float
}

И то и другое работают с числами с фиксированной точкой в packed формате. Для формата zoned нет типов в С/С++

Естественно, определена арифметика, преобразования типов.

Тут надо иметь ввиду что если вы определили число с фиксированной точкой длиной 5 знаков и пытаетесь присвоить ему значение из >5 знаков:

dcl-s var packed(5:0); // 5 символов, без запятой
var = 123456;          // пытаемся занести в него болше чем влезет

то получите системное исключение о переполнении.

Также (в С/С++ нет, но в других языках да) определены операции присваивания с округлением - работают там, где количество знаков после запятой у lvalue меньше чем у rvalue

dcl-s var1 packed(5:2);
dcl-s var2 packed(5:3);

var2 = 1.235;
var1 = var2;         // var1 = 1.23
eval(h) var1 = var2; // var1 = 1.24

Таким образом, если все делать по-хорошему, реализация чисел с фиксированной точкой тянет за собой достаточно много чего еще...

На мэйнфрейме z/Architecture, чтоль? Обработка десятичных чисел переменной длины -- фишка ещё с Системы 360...

Там тоже, вроде, есть, но это middleware IBM i (AS/400).

В целом для коммерческих расчетов полезное. Хотя все суммы всегд идут в миноритарных единицах. А для показа уже пересчитываются по справочнику валют - есть валюты где вообще "копеек" нет, есть где 1000 "копеек" в "рубле"...

Плюс packed и zoned соответствуют SQL-ным DECIMAL и NUMERIC

Вот да, чего не хватает так это decimal и bigint

Удобно еще универсальные нетипизированные определения типа *loval / *hival - минимальное/максимальное значение для данного типа. Т.е.

double d = *loval;
int i = *hival;
char c = *loval;

а компилятор уже сам нужное значение подставит...

Пример использования (в другом языке):

dcl-s result packed(15: 2);
dcl-s uplimit packed(15:2) inz(*hival);
dcl-s lowlimit packed(15:2) inz(*loval);
dcl-s interRslt packed(63:2);

// что-то вычисляем, результат в interRslt и потом
if interRslt in %range(lowlimit: uplimit);
  // присвоение безопасно - переполлнения не будет
  result = interRslt;
else;
  // фиксируем ошибку выхода за пределы возможных значений
endif;

Также удобно для инициализации результатов поиска минимального/максимального значения и много где еще...

универсальные нетипизированные определения типа

для такого есть std::numeric_limits из <limits> с его min, max, epsilon и прочими. Правда автовывод пока для них не работает, так что приходится явно указывать тип.

UFO just landed and posted this here

Можно сделать грязь.

#include <limits>
#include <iostream>

using namespace std;
template<typename T>
constexpr void tmin(T& v) {
    v = numeric_limits<T>::min();
}
int main() {
    float f;  tmin(f);
    int i;  tmin(i);
    double d;  tmin(d);
    cout <<i << " " << f << " " << d << endl;
    return 0;
}

Тут тип для параметра выводится. Возможно каким-то макаром можно его нормальным return value сделать, но я сходу не придумал.

UFO just landed and posted this here

Я в курсе что есть. А в С все это #define определено для каждого типа...

Я про то, что можно было бы сделать это на уровне компилятора - он-то все это "знает" на этапе компиляции.

Вообще, сейчас вот больше пишу на языке более высокого уровня (на С/С++ тоже приходится, но только то, что на нем проще и удобнее писать) и вижу, что там есть определенные плюсы.

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

Есть оператор clear - сброс значения переменной к дефолтному для денного типа значению. Есть reset - сброс значения переменной к значению которым она была инициализирована при объявлении (если не было явной инициализации, то reset == clear).

Со структурами очень гибко - можно явно размер указать (больше чем суммарный размер полей) - иногда бывает нужно. Можно явно инициализировать каждое поле по-отдельности. Можно явно указывать смещение поля внутри структуры (в т.ч. и перекрывать другие поля). Можно задавать неименованные поля (обозначаются *n) вместо всяких dummy, tmp. Например, шаблон для времени может выглядеть так:

dcl-ds dsTimeTemplate;
  Hours   char(2);
  *n      char(1) inz(':');
  Minutes char(2);
  *n      char(1) inz(':');
  Seconds char(2);
  Time    char(8) samepos(Hours);
end-ds;

Time объявлено как перекрывающее все остальные поля.

Разделители ':' объявлены как неименованные.

Заполняем Hours, Minutes, Seconds и получаем в Time готовую строку с разделителями.

Когда возвращаешься в С/С++ многих таких веще не хватает иногда. Т.е. там все это можно сделать, но нужно делать - класс прописывать, конструктор ему писать...

И уж есть С++ неуклонно движется в сторону более высокоуровнего языка, то хотелось бы в нем подобные вещи иметь.

В высокоуровневых языках для вставки значений в шаблон используется интерполяция строк. Кода получается на порядок меньше, чем вы написали.

Вопрос в том - быстрее ли? Здесь нет никакой интерполяции и никакого кода. Шаблон уже лежит в памяти, просто в нужные места вставляем и получаем результат.

В том и суть что очень много делается на этапе компиляции и относительно немного на этапе выполнения.

Определённо быстрее, ибо потом вам этот результат ещё руками копировать куда-то надо, чтобы использовать шаблон повторно. А интерполяция транслируется в код формирования строки сразу в нужном месте.

Никакого копирования.

Я объявляю

dcl-ds t_dsTimeTemplate qualified template;
  Hours   char(2);
  *n      char(1) inz(':');
  Minutes char(2);
  *n      char(1) inz(':');
  Seconds char(2);
  Time    char(8) samepos(Hours);
end-ds;

Ближайший аналог template - typedef в С. Т.е. описываю "тип" структуры.

Далее, где мне нужно уже объявляю переменную

dcl-ds dsTimeTemplate likeds(t_dsTimeTemplate) inz(*likeds);

Объявление переменной по шаблону с указанием правила ее инициализации "как прописано в шаблоне" - именованные поля инициализируются дефолтным для типа char значением (пробел), неименованные (разделители) - указанным для них значением ':'

И тут на этапе компиляции в памяти создается переменная dsTimeTemplate содержащая ' : : ' дальше остается просто заполнить поля нужными значениями

dsTimeTemplate.Hours   = '02';
dsTimeTemplate.Minutes = '13';
dsTimeTemplate.Seconds = '39';

И в dsTimeTemplate.Time получим '02:13:39'

Никаких "склеек строк", ничего. Просто присвоение (memcpy три раза по 2 символа). Разделители в нужных позициях расставлены на этапе компиляции. Для обращения ко всей строке целиком есть отдельное поле (аналог union в С, но более гибкий и наглядный).

Все это работает в обе стороны.

Например, есть тут такой тип - timestamp. Фактически - строка формата YYYY.MM.DD.HH:MM:SS.ssssss Можно для нее прописать аналогичный шаблон,

dcl-ds dsTimeStamp;
  Year char(4);
  *n char(1) inz('.');
  Month char(2);
  *n char(1) inz('.');
  Day char(2);
  *n char(1) inz('.');
  Hour char(2);
  *n char(1) inz(':');
  Minute char(2);
  *n char(1) inz(':');  
  Second char(2);
  *n char(1) inz('.');
  mSecond char(6);
  TS char(26) samepos(Year);
end-ds;

засунуть туда строку и получить готовую "раблюдовку" по ее составляющим. Без парсинга, без ничего. Просто удобный доступ к отдельным составляющим.

То есть у вас сначала идёт ещё и двойная инициализация полей.

При парсинге надо ситаксис чекать, а не просто на фиксированные подстроки разбивать. Да и на выходе надо числа иметь, а не просто подстроки.

Что именно и где надо - это от задачи зависит.

Где надо синтаксис - там есть свои инструменты. Где надо числа - там свои. Это просто в качестве наглядного примера.

Ну так расскажите, что это за задачи такие, где строка "12345678" должна парситься как 12 часов 45 минут и 78 секунд.

Вы валидацию с парсингом не путаете? Или для вас это одно и то же?

Для валидации есть

test(de) *cymd var;
if %error;
  // Это ерунда какая-то, но точно не дата в формате *CYMD
endif;

test(te) *hms var;
if %error;
  // Это не время в формате *HMS
endif;

Причем, var может быть как строкой, так и числом.

Если все ок, то дальше можно или втащить в шаблон и оттуда вытащить по частям (нужно число - ну сделайте %int(dsTimeTemplate.Hours))

А можно использовать встроенные

%SUBDT(value:*MSECONDS|*SECONDS|*MINUTES|*HOURS|*DAYS|*MONTHS|*YEARS)
%SUBDT(value:*MS|*S|*MN|*H|*D|*M|*Y)

Бывают ситуации когда приходит дата, но вы не знаете в каком она формате - может так, а может этак...

test(de) *eur var;
if %error;
  test(de) *iso var;
  if %error;
    // это вообще непойми что
  else;
    // Дата в формате *iso
  endif;
else;
  // Это дата в формате *eur
endif;

Причем функции эти реализованы в худшем случае на уровне системных API, в лучшем - на уровне машинных инструкций. И вряд ли вам удастся написать свой парсер который будет работать быстрее с учетом того, что предварительно придется определять по каким правилам парсить.

И да. Есть вещи, которые я на С реализую быстрее. Скажем, преобразование формата MITIME (это такая феерическая хрень - количество микросекунд от начала эпохи, середина которой приходится на 2000.01.01.00:00:00.000000) в строку timestamp. Но тут просто потому что системное API слишком универсально чтобы работать быстро.

Но в большинстве случаев, все-таки, наоборот. Уж за этим-то мы следим - есть специальные инструменты, которые позволяют и производительность и потребление ресурсов измерять достаточно точно.

Не знаю зачем вы пишете код, но без комментариев он никому не понятен. Для валидации вам всё равно придётся сперва распарсить строку. А что вы там собираетесь валидировать на явно не корректном синтаксисе совершенно не ясно.

test(de) - встроенная валидация даты (d) с фиксацией ошибки (e - без e будет кидаться системное исключение, с e устанавливается флаг %error подробности можно посмотреть в переменной %status).

Опирается оно на какие-то там системные потроха. И работает со всеми мыслимыми форматами:

*EUR - 16.05.2023

*ISO - 2023-05-16

*CYMD - 1230516

и еще много чего там может быть. Отвечает на вопрос "является ли это датой в данном формате".

Т.е. строка '2023-05-16' или число 20230516 будут валидны для формата *ISO

Строка '16.05.2023' или число 16052023 валидны в формате *EUR

Как оно это делает внутри - ну как-то делает :-) И собственно парсить ее нам в данной задаче может быть и не нужно. Только знать что дата валидная. Если нужно - ну зная формат просто наложим на шаблон и все.

В высокоуровневых языках парсер дат сразу выдаёт структуру с которой можно работать, не зная в каком именно из 10 форматов дата была написана и не требуя допарсивать потом руками, чтобы вытянуть нужные значения.

Хорошо. Не зная формата:

10121012 - это 1012-10-12 (12 октября 1012-го года) в *ISO или 10-12-1012 (10 декабря 1012-го года) в *EUR?

Можно и позаковыристее придумать пример, но лень.

И опять - часто не нужно ничего вытягивать. Нужно просто быть уверенным что это корректная дата. И ней потом можно работать. Например, в БД положить. А вытягивать из нее будут потом уже (условно - валидация один раз при занесении в БД, вытягивание - 100500 миллионов раз при последующих обращениях). Вы серьезно считает что в условиях высокой нагрузки лучше ее каждый раз потом прогонять через парсер вместо того чтобы положить в шаблон и автоматом получить (фактически даром) нужные элементы?

Валидация и разбивка на компоненты - разные операции. Мешать их в кучу -ошибка (увы, типичная ошибка).

На выходе из парсера приходит структура из чисел с которыми умеет работать процессор. Это не про "валидацию" (семантический анализ) и тем более не про "разбивку на компоненты" (лексический анализ). И чтобы процессору работать с этими вашими "zoned числами", компилятору приходится вставлять парсинг чисел на лету, что не очень эффективно.

И чтобы процессору работать с этими вашими "zoned числами", компилятору приходится вставлять парсинг чисел на лету, что не очень эффективно

Не совсем понял о чем вы?

Компилятор просто преобразует код на языке в набор машинных инструкций (MI). Которые потом уже системой преобразуются в исполняемый код (с этим там тоже не все так просто, но это уже дебри). Работа с числами с фиксированной точкой (арифметика, преобразования и т.п.) реализованы на уровне MI. Так что никакого "парсинга на лету" компилятор не вставляет. Работа с этими форматами реализована на уровне системы (что там может сам процессор - загадка - таки там Power9 стоит...).

Но речь-то о другом сейчас. Речь о том, что как максимально быстро и с минимальными затратами ресурсов раскидать дату на составляющие.

Поскольку все это происходит в условиях hiload где по определению избегаем любых лишних телодвижений, вариант с каким-то парсером-валидатором не есть хорош. Потому что данные валидируются один раз на входе, а потом с ними много работают уже в уверенности что там все ок. И если на валидацию можно потратить побольше времени, то раскладку по компонентам надо уметь делать максимально быстро. И быстрее чем наложить шаблон, дающий прямой доступ к "полям" даты или времени я не знаю...

И в который раз ДА - на С все это можно сделать на union. Но вот хоть режьте, концепция где структура есть область памяти в которой можно свободно расставить поля с указанием типа и смещения от начала структуры, мне импонирует больше т.к. дает больше свободы, компактнее и прозрачнее чем механизм union'ов в С.

И опять ДА - я не сравниваю языки какой лучше или хуже. Я сравниваю отдельные возможности. Чего-то мне не хватает в том языке на котором больше пишу сейчас (последние 6 лет), чего-то не хватает в С/С++ (на которых я и сейчас иногда пишу, а до этого писал лет 25...).

Ну вот пример. Есть дата (она уже провалидирована ранее) в формате *CYMD это 7-значный формат для хранения дат в диапазоне 1900-01-01.00:00:00 - 2999-12-31.23:59:59.

Выглядит как CYYMMDD где C = 0 для дат 20-го века, 1 для дат 21-го века.

Т.е. для 17-го июня 2023 года это будет число 1230617

Традиционно у нас такие даты хранятся в переменной типа zoned(7:0) (что такое zoned внутри тут приводил картинку).

И вот из этой даты нужно извлечь год.

По типам данных

ind (индикатор) - логический тип данных. Фактически - char(1) у которого возможны два значения - '1' - *on и '0' - *off

uns(3) - unsigned char - беззнаковое однобайтовое целое

uns(5) - unsigned short - беззнаковое двухбайтовое целое

Функция:

dcl-proc getCYMDYear;
  dcl-pi *n int(5);
    zDate zoned(7: 0) value;
  end-pi;

  dcl-ds dsZonedDate qualified;
    zDate   zoned(7: 0) inz;
    cent21  ind         pos(1);
    yearHi  uns(3)      pos(2);
    yearLo  uns(3)      pos(3);
  end-ds;
  
  dcl-s mask  int(3)  inz(15);
  dcl-s year  int(5);

  dsZonedDate.zDate = zDate;

  // Используем особенности формата zoned
  // дата 17 Июня 2023 (17-06-2023) будет представлена как
  // 1230617 а в памяти лежать в виде F1F2F3F0F6F1F7
  // Надо взять второй байт, сделать побитовое И с маской 0F,
  // умножить на 10 и прибавить результат побитового И третьего
  // байта с маской 0F
  year = %bitand(dsZonedDate.yearHi: mask) * 10 
       + %bitand(dsZonedDate.yearLo: mask);

  // Добавляем столетие
  // Если первый символ '0' (*off) - 20-й век
  // Если '1' (*on) - 21-й
  if dsDate.cent21;
    year += 2000;
  else;
    year += 1900;
  endif;

  return year;
end-proc;

Итого: два побитовых И, одной умножение, два сложения. Все.

Да, не портабельно. Но задачи портабельности не ставится. Ставится задача чтобы работало максимально эффективно на данной конкретной платформе.

Наложили на шаблон и сразу получили доступ к нужным элементам уже в нужном типе.

Вот почему в С сразу вместо union'ов не использовали такую концепцию описания структур? Вот битовые поля есть, а байтовых нет... Зачем было придумывать лишнюю сущность (union)?

Почему сейчас нельзя это реализовать с С++?

Скорее вы описываете union типы. Правда в случае с С++ чтобы написать ровно такое же придётся написать заметно больше кода. Ну и там будут не явные поля, а методы с вьюхами. Не думаю, что в языке появится что-то совсем аналогичное, т.к. оно не слишком general purpose. Теоретически в 20+ стандартах можно попробовать что-то по аналогии с format сделать.

Ну да, типа union, но гибче. Здесь структура (ds) рассматривается как некий массив байт в котором можно для каждого поля (независимо от остальных) указывать его положение в структуре и тип. Никто не запрещает перекрытия полей.

Как уже писал выше, есть тип с плавающей точкой zoned для которого (для положительных чисел) содержимое в памяти экивалентно строковому представлению числа. Т.е.

dcl-ds dsStrZoned;
  zField zoned(7:0);
  strField char(7) pos(1); // или samepos(zField)
end-ds;

при занесении туда строки dsStrZoned.strField = '1234567' в числовом поле dsStrZoned.zField окажется число с фиксированной точкой 1234567.

Такой вот простой и понятный механизм. И очень универсальный.

Из практического примера. 20-значный банковский счет. Первые 5 цифр - тип счета (иногда называют "счет второго порядка"). Затем три цифры - код валюты счета. Пишем:

dcl-ds dsAccount;
  accNo    char(20);
  accType  char(5) pos(1);
  currCode char(3) pos(6);
end-ds;

Присваиваем номер счета dsAccount.accNo и автоматом получаем отдельно его 5-значный тип в dsAccount.accType и код валюты счета в dsAccount.currCode без каких-либо затрат на извлечение составляющих.

Да, в С все это тоже несложно через указатели на смещение относительно начала структуры. Но не так прозрачно.

И уж есть С++ неуклонно движется в сторону более высокоуровнего языка

Но это не точно.

Ну мне казалось, что цель всего вот этого вот - сделать разработку быстрее, проще и безопаснее.

Насколько получается... Ну не знаю. Я не сильно за новшествами слежу т.к. с 17-го года С/С++ для меня "второй язык", не основной. И пишу на нем далеко не каждый день. И даже больше на С или "С с классами" чем на полноценном С++ т.к. тут речь не о сложной логике - для этого есть более удобный язык, но о всяких достаточно низкоуровневых вещах, плотном взаимодействии с системой.

Ну мне казалось, что цель всего вот этого вот - сделать разработку быстрее, проще и безопаснее.

Так оно и есть. Но на какой-то принципиально новый уровень высокоуровневости языка пока так и не вышел. Возможно, когда в C++ завезут рефлексию, тогда в каких-то моментах станет сильно лучше, но все равно от заботы за тем, чтобы память не утекала, ссылки не протухали, а исключения не вылетали из noexcept-контекстов, останутся на программисте. Не говоря уже про россыпь UB :)

Если я правильно понял, что вы тут делаете, то на высокоуровневом языке это выглядит как-то так:

short result;
int interResult = 1234567;

try {
  // что-то вычисляем, результат в interRslt и потом
  result = interResult.to!short;
} catch( ConvOverflowException error ) {
  // фиксируем ошибку выхода за пределы возможных значений
}

Я, конечно, приветствую ещё одну статью, из разряда много-численных, исследующих "реализацию целых чисел в различных ЯП-ах". Так повелось, но эти реализации отличаются друг от друга лишь количеством бит (байт), выделенных под целое число. Длинной арифметики ( см. https://ru.wikipedia.org/wiki/Длинная_арифметика ) и даже поиска в Google ( см. https://www.google.com/search?q=целые+числа+произвольной+длины ) чтобы раз и навсегда закрыть тему "в такой фундаментальной области, как работа с целочисленными типами", явно недостаточно. Необходимо требуются инновации, и, несомненно, освоение государственных грантов: на глубокое исследование темы, с привлечением ИИ и "глубоких нейросетей", и, возможно, блокчейна с NFT-токенами. В общем, есть куда развиваться.

А вот это, как мне кажется, ноу-хау и даже тема для диссертации:

"Итак, вот чего я хочу: знаковое целое число, где INT_MIN == -INT_MAX, полученное путем перемещения минимального значения на единицу вверх.

Во-первых, вы не теряете ничего полезного: Я бы сказал, что дополнительное отрицательное число не имеет значения в большинстве случаев. В конце концов, если бы у вас было лишнее число, разве не логичнее было бы иметь дополнительное положительное число вместо отрицательного?"

Для представления отрицательных целых чисел используется не обратный, а дополнительный код не от неча делать, а из-за того, что этим резко упрощается аппаратура. Что же до переполнения, то нет никаких непреодолимых технических проблем его обнаруживать, и то, что в Си/Си++ это не принято, не означает, что это невозможно.

Автора бить Кнутом. Всеми четырьмя томами по голове, (еще Генри Уоррена добавить для надёжности, жаль тонковат).

Фишка С\С++ в хорошем соответствии ассемблеру, то есть системе команд, то есть архитектуре. Причем не x86 или ARM скажем, а всем им сразу, в том числе и там где памяти и мощей с гулькин нос, но давайте вот это всё эмулировать.

При чем здесь Кнут? Предложенные автором типы никак не уменьшают производительность. Даже больше, такой тип, это дополнительная информация о типе. Для оптимизирующего компилятора такой тип, если он является встроенным - это громадное пространство для оптимизаций, в том числе и упаковка std::optional в sizeof(int).

Ну и C и C++ - это языки высокого уровня. Ни больше ни меньше. Соответствие ассемблеру вещь весьма растяжимая. Пространство для compile time оптимизаций у C++ не такое большое, как принято считать. Да, Java или C# безнадежно хуже из-за ссылочных типов данных или своей модели памяти. Да и вообще, собрать их "без рантайма" ещё нужно постараться.

Но это не значит, что C и C++ идеальны или достаточно хороши в этом плане. Чтобы "ускорить" эти языки добавили дыры (UB) в стандарт, чтобы у компилятора было больше информации. Например, числовые знаковые числа в языках C и C++ довольно абстрактны. В стандарте не определено, что будет происходить при переполнении. Может происходить вообще всё что угодно.

О каком соответствии ассемблеру идёт речь? Различные системы команд могут разительно отличаться. Регистр с флагом переполнения может быть, а может и не быть. Может использоваться совсем другая модель памяти с несколькими адресными пространствами.

Там где мощностей мало, без расширений семантики С или C++ достаточно сложно писать. Иначе, когда тебе нужно сделать какую-то специфичную для процессора вещь, ты просто теряешься в догадках, а не сломается ли твой код из-за UB, когда тебе нужно потрогать стек поинтер.

Добро пожаловать на Хабр.
Кнут при том, что понимает и алгоритмы, и архитектуру. Дополнительный код занял доминирующее положение не по чьему-то произволу, а потому, что это на практике лучший способ проецирования целых чисел на битовые строки. За подробностями - в "Исскуство программирования", или в "Hackers Delight" Уорена.

в хорошем соответствии ассемблеру

Лол, а какому ассемблеру? PDP? VAX? MIPS? ARM? Intel? m68k? Си изобретался именно для того чтобы можно было отдалиться от ассемблера и писать универсальный код, который впоследствии превращается в конкретный ассемблер для конкретной архитектуры и всё соотвествие находится в компиляторах, а не в языке.

По-моему из перечисленного вами, всё кроме самых ранних PDP использует дополнительный код.

Новый дополнительный тип конечно потребует эмуляции поначалу. Но если это реально будет удобно и эффективно, разработчики процессоров подтянутся.

Я не к тому что я поддерживаю предложение автора, но аргумент "этого сегодня нет в железе" это неправильный аргумент.

Ненормализованные вещественные числа обеспечивают бо́льшую часть того, что он описал. В старинных машинах вроде CDC-6600 и БЭСМ-6 их фактически и использовали вместо целых. Потом почему-то от этой практики отказались, автору неплохо бы изучить причины. IBM озолотилась, когда вместо вот этого всего придумала современную систему с униформными битами и байтами.

Древним людям такой ход развития инженерной мысли простителен, балбесу из 2023 года – нет.

UFO just landed and posted this here

Идите дальше и используйте арифметику с насыщением, где 127 означает переполнение сверху, а -127 - снизу.

UFO just landed and posted this here

Зашёл ожидая увидеть предложения о нормальных (не тайпдефах, которые везде разные и от того из не поиспольщуешь в публичном api) типах фиксированной длины, без неявного преобразования (которые уже есть в кланге в виде ExactInt, емнип, и которые закрывают п.2 статьи), а увидел что-то странное, косвенно свидетельствующее о том, что автор не понимает вообще как работают процессоры, компиляторы и язык C++.

Да, давайте превратим поле целых чисел в непонятно что, что ни в мапу не положишь, ни отсортируешь (неговоря уже об эффективности всего этого дела), лишь для "симметричности яичек" xD

По сути, ничего не поменялось с функциональной точки зрения, просто UB сдвинулось из abs куда-то выше по цепочке. В одном месте убавили, в другом прибавили. Проше ее стало, только наоборот.

Если это сделать, придется выбросить весь ленаси код и обратную совместимость. Забудьте, никто никогда на это не пойдёт

Ну почему же, просто появится новый тип. Старые типы останутся тоже. Совместимость здесь никак не пострадает.

Я не к тому что я поддерживаю предложение автора, но аргумент "пострадает обратная совместимость" неверен.

Я бы хотел эффективную встроенную поддержку чисел произвольной длины
(причём длину указывать желательно с точностью до бита). Как фиксированной длины во время компиляции, когда память жёстко выделяется типа std::array, так и динамической, типа std::vector.

Мне кажется, что в современном С++ это можно сделать на уровне библиотеки и работать с такими числами почти как с встроенными. Вот моя попытка написать такое для целых - Simple long integer math library for C++. Посмотрите, может подойдет.
Из того, что вам нужно, там нет точности до бита и динамического изменения размера, так как я пока не знаю как выполнить эти требования и при этом быть сравнимым по эффективности с встроенными типами.

constexpr auto uo = 03766713523035452062041773345651416625031020_ui128;  // octal unsigned integer
constexpr auto ud = 338770000845734292534325025077361652240_ui128;       // decimal unsigned integer
constexpr auto uh = 0xfedcba9876543210fedcba9876543210_ui128;            // hexadecimal unsigned integer
constexpr auto io = -03766713523035452062041773345651416625031020_si128; // octal signed integer
constexpr auto id = -338770000845734292534325025077361652240_si128;      // decimal signed integer
constexpr auto ih = -0xfedcba9876543210fedcba9876543210_si128;           // hexadecimal signed integer

constexpr auto u = 0xfedcba98'76543210'fedcba98'76543210_ui128;          // hexadecimal unsigned integer

Sign up to leave a comment.