Замечания и предложения

Всё это обсуждение началось именно с задаче, где спрашивались только индексы.
Ну сделайте уже хотя бы для IList, а не для массивов…

Да, именно так.

Уже не сказал а сделал:

1 лайк

Да, но для массива. Распространить на TList - да, разумно, С другой стороны, это не срочно, потому что списки TList, увы, менее популярны, чем массивы.

Не TList а IList. IList это интерфейс, от которого много что наследует. Но из самого популярного - массивы и просто List.

Если придираться к словам, то не наследует, а реализует (если речь ведётся про классы и записи). В случае же интерфейсов, где IList<T> - базовый - термин верен.

Прошу добавить математическое округление, например, так:

/// Возвращает x, округленное до ближайшего целого по математическому округлению.
function MathRound(x: real): integer;
begin
  if (Frac(x) = 0.5) then Result := Trunc(x + 0.5)
  else if (Frac(x) = -0.5) then Result := Trunc(x - 0.5)
  else Result := Round(x)
end;
/// Возвращает x, округленное до ближайшего вещественного по математическому округлению с digits знаками после десятичной точки
function MathRound(x: real; digits: integer): real;
begin
  if (Frac(x) = 0.5) then Result := x + 0.5
  else if (Frac(x) = -0.5) then Result := x - 0.5
  else Result := Round(x, digits)
end;
/// Возвращает x, округленное до ближайшего длинного целого по математическому округлению
function MathRoundBigInteger(x: real): BigInteger;
begin
  if (Frac(x) = 0.5) then BigInteger.Create(x + 0.5)
  else if (Frac(x) = -0.5) then BigInteger.Create(x - 0.5)
  else BigInteger.Create(Math.Round(x))
end;

/// Возвращает число, округленное до ближайшего целого по математическому округлению
function MathRound(Self: real): integer; extensionmethod;
begin
  if (Frac(Self) = 0.5) then Result := Trunc(Self + 0.5)
  else if (Frac(Self) = -0.5) then Result := Trunc(Self - 0.5)
  else Result := Round(Self)
end;

/// Возвращает x, округленное до ближайшего вещественного по математическому округлению с digits знаками после десятичной точки
function MathRound(Self: real; digits: integer): real; extensionmethod;
begin
  if (Frac(Self) = 0.5) then Result := Self + 0.5
  else if (Frac(Self) = -0.5) then Result := Self - 0.5
  else Result := Round(Self, digits)
end;

/// Возвращает число, округленное до ближайшего длинного целого по математическому округлению
function MathRoundBigInteger(Self: real): BigInteger; extensionmethod;
begin
  Result := MathRoundBigInteger(Self);
end;

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

Это не так делается.
Вообще, если я правильно помню - стандартное округление банковское. То есть если Frac=0.5 - округляет до ближайшего чётного, а не до Int(x).

Если нужно математическое - используйте System.Math напрямую (Round его вызывает):

System.Math.Round(x, System.MidpointRounding.AwayFromZero);
System.Math.Round(x, digits, System.MidpointRounding.AwayFromZero);

Если использовать напрямую - будет ещё и быстрее выполняться.

А как исправить ошибку, что, начиная с x=2.4999999999999999, Writeln(System.Math.Round(x, System.MidpointRounding.AwayFromZero)) выводит 3?

С real - никак, потому что число 2.4999999999999999 не может быть представлено в виде real:

begin
  var x := 2.4999999999999999;
  x.ToString('N99').Println; // 99 знаков после запятой, больше .ToString не принимает
end.
begin
  var x := real.Parse('2.4'+'9'*13); // больше девяток точность real не позволяет, из за чего округлит ещё в Parse. Компилятор вызывает real.Parse когда встречает в коде литерал из цифр и точки
  x.ToString('N99').Println;
end.

Из стандартных типов - точность больше только у decimal:

begin
  var x := decimal.Parse('2.4'+'9'*27);
  x.ToString('N99').Println;
end.

decimal ещё и хранит своё значение в 10-ичной СС, в отличии от real, которое хранится в 2-ичной СС. Это так же помогает сделать округление более адекватным в таком случае:

begin
  var x: real;
  loop 999 do x += 0.001; // 0.001 нельзя красиво представить в 2-ичной СС, потому что это 1/2/2/2/5/5/5, а 1/5 имеет бесконечность цифр после запятой в 2-ичной СС
  x.ToString('N20').Println;
end.
begin
  var x: decimal;
  var d := decimal(0.001); // а из за того что `decimal` хранится в 10-ичной СС - тут "0.001" сохранит без погрешности
  loop 999 do x := x+d;
  x.ToString('N20').Println;
end.

Но в отличии от real - decimal редко аппаратно поддерживается, поэтому действия с ним дольше считаются. Ну и памяти на большую точность, ещё и в 10-ичной СС, конечно, надо больше.


Из альтернатив:

  1. Описать тип дроби:

    type
      Дробь = record
        числитель, знаменатель: BigInteger; // BigInteger это целое, которое вместо переполнения выделяет себе больше оперативной памяти
        
        //ToDo и остальные операторы
    //    static function operator+(x,y: Дробь): Дробь;
        
      end;
    

    Таким образом точность получается бесконечная. Но при операциях вроде Sqrt или Sin всё равно придётся решить как округлять, потому что их результат не всегда можно представить дробью.

  2. Можно упростить, считая что знаменатель это всегда 10^N, где N - число цифр точности:

    type
      ОченьТочноеЧисло = record
        private val: BigInteger;
        private constructor(val: BigInteger) :=
        self.val := val;
        private const N = 100; // точность - N цифр после запятой
        private static неявный_знаменатель := BigInteger.Pow(10, N);
        
        public static function operator+(x,y: ОченьТочноеЧисло): ОченьТочноеЧисло :=
        new ОченьТочноеЧисло(x.val+y.val); // намного проще чем для дробей
        
        public static function operator*(x,y: ОченьТочноеЧисло): ОченьТочноеЧисло :=
        new ОченьТочноеЧисло(x.val*y.val div неявный_знаменатель); // но при умножении и делении - всё равно надо помнить про знаменатель
        
      end;
    

По крайней мере это 2 простейших альтернативы, которые я знаю. А так придумать можно что угодно, на что фантазии хватит.

Но, опять же, всё это будет очень медленно считаться, потому что аппаратная поддержка BigInteger вообще невозможна, из за того что его размер изменяется по случаю.

Это не ошибка, это особенность компьютерного представления данных с плавающей точкой. Если Вам нужна абсолютная точность - работайте с простыми дробями. Напишите себе библиотеку или для начала попробуйте использовать библиотеку NumLib.ABC - там реализован класс простых дробей “бесконечной” точности.

Такое предложение, чтобы вкладки IDE закрывались средней кнопкой мыши. Это трудно организовать?

Ну и Ctrl+W тогда. Давно оба просят, но разработчики не хотят на это время тратить.

Если очень надо - редачьте тут и кидайте пулл.

В таком виде это не предложение. Это хотелка. А предложение должно содержать обоснование. Которое отличается от “я хочу”.

Тема называется “Замечания и предложения”. Вот я и предложил. И, видимо, не я первый. Да, мне кажется это было бы удобно одно нажатие вместо двух, как и принято во многих редакторах плюс еще комбинация на клавиатуре тоже не помешала бы. А по большому счету хорошо бы вообще написать плагин(ы) для паскаля абц под, например, Visual Studio Code.

Admin: 2 Bronislav - замечание - переход на личности.

И плагины тоже предлогали, только, опять же, никто не хочет делать это, все только предлагают.

Ну а я вообще считаю что IDE должна быть не плагином и даже не переделанным огрызком SharpDevelop как сейчас.

Нет нормальной документации, чтобы писать плагины. Впрочем, такая ситуация не только с плагинами. С API компилятора тоже. Но, всё лишь потому, что разработка подобных документаций не была целью разработчиков.

Стоит ли удивляться почему

? Не думаю.

Возьмётесь?

В проекте есть подпроект TestPlugin. Это минимальный плагин. Его интерфейс крайне прост. На его основе написан плагин CodeTemplatesPlugin. Если кто-то соберётся писать плагины и что-то будет непонятно, заведите топик на форуме.

Откомпилированные плагины должны оканчиваться на VisualPascalABCPlugin и находиться в папке bin.

Фактически это вся документация.

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

Я полностью согласен. Обоснуйте. Почему это важно, а не “я хочу”

Я имел ввиду под документацией такую же, какая есть для Visual Studio для написания плагинов.

Вы странным образом сравниваете PascalABC.NET со Студией ещё со времён публикации на Хабре. Это несопоставимые ресурсы ни по количеству разработчиков ни по стоимости разработки.

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

Следует отметить, что в Студии плагины делаются очень мудрёно даже с документацией. И что самое плохое - каждые два года выходит новая Студия и она кардинально меняет интерфейс плагинов. Все старые плагины ложатся.