Помощь новичкам

Классный ответ. Подрозумевается что вы кликали какие то ссылки к определённым сайтам и просматривали те страницы. Или не просматривали?

Видимо, да. Вот, что-то нашёл, но это не универсально: Write(x:0:6). Видимо, документы пока никакие. А как сформулировать вопрос, чтобы найти эти документы?

В Pascal ABC NET есть тип float? Если он есть, где в справке о нём написано? Почему для перевода real в строку используется функция FloatToStr(a: real): string, а не RealToStr(a: real): string? Почему не работает var x:decimal:=1,5, но работает var x:decimal:=decimal.Parse('1,5')? Почему decimal поддерживается не везде?

Это устаревший и лично паскальный синтаксис.

А я спрашиваю - какие документы по .Net вы нашли и прочитали.

Судя по вопросам, человек не читает ничего, кроме каких-то интернет-ссылок. Достаточно ту же мою книгу открыть и найти там все что нужно по контексту, даже .NET не листая. Но он и этого не хочет. Все, умываю руки.

float это single, real это double. Основные типы целых и чисел с плавающей запятой - аппаратно поддерживаемые. BigInteger и decimal к ним не относятся. Но у них и имена одинаковые в C# и паскале, как раз потому что они реализованы как пользовательские записи.

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


В C# часто используют float, потому что привыкли к 32-битникам. Но вообще всё что работает с 32-битными числами - работает и с 64-битными.

Точнее, все фичи .Net . Глобальные подпрограммы - это фичи паскаля. FloatToStr, разумеется, не подразумевает тип float из вообще другого языка. “floating point number” переводится как “число с плавающей запятой”.


Про decimal вообще отдельная история. Для начала разберитесь зачем он и в чём его особенности. Большинство ответов на ваши вопросы - станут само собой разумеющимся. И старайтесь, по крайней мере, писать нормальный код в примерах. Больно смотреть на 2 синтаксические ошибки в 2 коротеньких кодах.


Возвращаясь к теме, которую вы попытались забросать кучей офтоп вопросов:
Я держу открытую страницу документации, которую вам надо было найти. И держу в голове всю информацию, которую может быть не тривиально найти.

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

(1) Скачайте бесплатно книжечку отсюда

(2) Пролистайте до п. 6.2.26.2 “Составное форматирование” (стр. 247)

(3) Прочитайте до п. 6.2.26.5 “Интерполированные строки” включительно.

А вы для начала распишите decimal.Parse(‘0,50’) и decimal.Parse(‘0,5’) и посмотрите какие это на самом деле значения

Разделитель — точка, а не запятая.

Ну либо можно в начале программы прописать

System.Globalization.CultureInfo.CurrentCulture := System.Globalization.CultureInfo.Create('Ru-ru');

Или только конкретное

System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator := ',';

Чтобы разделителем была запятая.

Да. Неявное приведение там убрали давным-давно из-за каких-то багов.

Чтобы решить вопрос

я пытался написать так:

  var a:real:=real.Parse('6.103515625E-05');
  Writeln(a);
  WritelnFormat('{0:f25}',a);

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

Где поддерживается decimal?

Почему на изображении в сообщении, которое отправил @Kotov, написано, что приводить в decimal — “на страх и риск”?

Я не вижу округления в вашем примере. Какое число вы дали в real.Parse - такое и выводит.

Потому что decimal хранит числа не в том же формате что real. Я говорю, для начала найдите чем этот тип особенный и где используется. Это уже ответит на большинство вопросов.

Тогда вот, например, WritelnFormat('{0:f1}', 1.15); на выводе даёт 1.2. Было 1.15, стало 1.2, чем не округление?

Где-то он записывается так: один бит под знак, некоторое количество битов под значение (целое число) и некоторое количество битов на хранение позиции, в которой как бы должна стоять точка между целой и дробной частью в значении. (Я правильно запомнил?)

В операциях с финансами, где погрешность должна быть нулевой, кроме того, “если число нецелое, а точности в 16 знаков, которые даёт тип real, недостаточно.”

Как формат может испортить данные?

А, теперь понял что вы имели в виду. Ну значит есть несколько вариантов:

  1. Заранее посчитать кол-во цифр после запятой;
  2. Использовать :f99, но затем .TrimEnd('0');
  3. Написать свой метод превращения в строку.

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

Если бы мне самому приспичило выводить все доступные цифр - я бы использовал 3. вариант. И не вижу в этом ничего плохого, раз это не общий случай.

Это так все числа с плавающей запятой устроены. single от real отличается только кол-во бит мантисы (целое число) и экпоненты (степень, то есть позиция точки).

Вот только single и real хранятся в 2-ичной СС. И позиция точки в них определяется именно в записи в 2-ичной СС.

Из за этого, к примеру, 1/5 невозможно представить в real без погрешности. Так же как 1/3 в 10-ичной СС, потому что 10^n, где n-целое - не может делится на 3.


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

Повышенная точность decimal - только следствие. 8 байт было мало для 10-ичной СС, поэтому взяли 16. Но даже если бы у decimal была бы так же та же точность что у real - для финансов этого бы тоже хватило.


Теперь к переводу из real:
Кроме того что decimal не поддерживается на уровне процессора, а значит перевод (как и любая последующая арифметика) нуждается в куче математики и тратит на это некоторую производительность…

Как вы думаете, что будет если перевести 1/5 в 2-ичной СС, а затем назад в 10-ичную? Может погрешность и аннулирует саму себя при переводе туда-сюда. А может и нет.

Я придумал это, но мне бы сделать это в подпрограмме, не требующей что-либо в качестве результата (переменная Result).

function WriteFormatFixedPoint(x: real): boolean;
begin
  Result := True;
  if (Abs(Frac(x)) = 0) then WriteFormat('{0:f0}', x)
  else if ((Abs(Frac(x)) >= 1E-1) and (Abs(Frac(x)) < 1)) then WriteFormat('{0:f1}', x)
  else if ((Abs(Frac(x)) >= 1E-2) and (Abs(Frac(x)) < 1E-1)) then WriteFormat('{0:f2}', x)
  else if ((Abs(Frac(x)) >= 1E-3) and (Abs(Frac(x)) < 1E-2)) then WriteFormat('{0:f3}', x)
  else if ((Abs(Frac(x)) >= 1E-4) and (Abs(Frac(x)) < 1E-3)) then WriteFormat('{0:f4}', x)
  else if ((Abs(Frac(x)) >= 1E-5) and (Abs(Frac(x)) < 1E-4)) then WriteFormat('{0:f5}', x)
  else if ((Abs(Frac(x)) >= 1E-6) and (Abs(Frac(x)) < 1E-5)) then WriteFormat('{0:f6}', x)
  else if ((Abs(Frac(x)) >= 1E-7) and (Abs(Frac(x)) < 1E-6)) then WriteFormat('{0:f7}', x)
  else if ((Abs(Frac(x)) >= 1E-8) and (Abs(Frac(x)) < 1E-7)) then WriteFormat('{0:f8}', x)
  else if ((Abs(Frac(x)) >= 1E-9) and (Abs(Frac(x)) < 1E-8)) then WriteFormat('{0:f9}', x)
  else if ((Abs(Frac(x)) >= 1E-10) and (Abs(Frac(x)) < 1E-9)) then WriteFormat('{0:f10}', x)
  else if ((Abs(Frac(x)) >= 1E-11) and (Abs(Frac(x)) < 1E-10)) then WriteFormat('{0:f11}', x)
  else if ((Abs(Frac(x)) >= 1E-12) and (Abs(Frac(x)) < 1E-11)) then WriteFormat('{0:f12}', x)
  else if ((Abs(Frac(x)) >= 1E-13) and (Abs(Frac(x)) < 1E-12)) then WriteFormat('{0:f13}', x)
  else if ((Abs(Frac(x)) >= 1E-14) and (Abs(Frac(x)) < 1E-13)) then WriteFormat('{0:f14}', x)
  else if ((Abs(Frac(x)) >= 1E-15) and (Abs(Frac(x)) < 1E-14)) then WriteFormat('{0:f15}', x)
  else if ((Abs(Frac(x)) >= 1E-16) and (Abs(Frac(x)) < 1E-15)) then WriteFormat('{0:f16}', x)
  else Result := False;
end;

function WritelnFormatFixedPoint(x: real): boolean;
begin
  Result := WriteFormatFixedPoint(x);
  Writeln;
end;

Почему

begin
  WritelnFormatFixedPoint(real(100000000000009.9));
  Writeln(real(100000000000009.9));
end.

выводит

100000000000010.0
100000000000010

хотя должно вывести

100000000000010
100000000000010

?

А процедуры для кого?

Я только что объяснил, пока вы писали.

И кстати, тип 123.456 - это уже real. Дополнительный перевод в real игнорируется компилятором.

Все нижеприведённые утверждения верны? Если нет, опишите подробнее, вместо того, чтобы писать “Нет.”

  1. Наибольшей точностью из всех типов, которые могут по умолчанию записываться в экспоненциальном виде, обладает real (double).
  2. У real точность равна 15–16 значащих цифр, поэтому в нижеприведённом коде не имеет смысла писать :f17 и больше.
  3. String вместит число любой длины.
  4. Нижеприведённая функция не имеет недостатков, багов (например, неудаление лишних символов или удаление нужных), подходит для всех вещественных типов и достаточно хорошо справляется со своей задачей.
  5. У вас нет предложений по её улучшению (даже по улучшению названия).

function FixedPoint(x: real): string;
begin
  Result:=Format('{0:f16}', x).TrimEnd('0').TrimEnd('.');
end;

Вы имеете в виду литералы? Они всегда real, если в числе есть точка. Тут идёт преобразование из real в single:

begin
  var x: single := 1.2;
end.

(скорее всего, JIT оптимизирует это и заменит на константу типа single, но в .exe 1.2 хранится как константа типа real)

Точка в real можно стоять вне числа, добавляя нолики с 1 из 2 сторон. Поэтому нет. По моему, f99 это максимум, что принимает. Хотя у real, вроде, может быть 308 цифр (из которых значущими будут таки только 15-16).

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

Но при этом парсинг и real, и decimal из строки - это медленно.

Format это для интерполированных строк. Когда нужно только 1 значение - лучше x.ToString('f99').

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

Вообще, хоть в C# и нет неявного приведения в decimal, но зато там есть литералы этого типа с суффиксом m.

Этот decimal весьма неудобен в работе. Кривой какой-то. Настоящий decimal - он в СУБД. А в языках программирования реальный decimal последний раз я видел в 80-х в PL/1. Но он был сделан под IBM\360\370, где процессор аппаратно поддерживал десятичную арифметику.

Именно по этой причине критики PL/1 обожали приводить пример 15 + 1/3, дававший 5.33333… 3

Можно после операции обрезать не нужные знаки, только зачем?

System.Math.Round

Это что?

В наш век это не исключения. 5/0=+∞, -5/0=-∞, 0/0=NaN. Всё это особые числа, но исключений они не дают, что позволяет в некоторых местах перехватывать исключительные ситуации позже. А это упрощает код и уменьшает кол-во проверок.

Ну и а если речь про decimal - напишите программу с делением на 0 и посмотрите тип исключения. Ну и затем используйте try.