Классный ответ. Подрозумевается что вы кликали какие то ссылки к определённым сайтам и просматривали те страницы. Или не просматривали?
Видимо, да. Вот, что-то нашёл, но это не универсально: 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, недостаточно.”
Как формат может испортить данные?
А, теперь понял что вы имели в виду. Ну значит есть несколько вариантов:
- Заранее посчитать кол-во цифр после запятой;
- Использовать
:f99
, но затем.TrimEnd('0')
; - Написать свой метод превращения в строку.
Конечно, 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
игнорируется компилятором.
Все нижеприведённые утверждения верны? Если нет, опишите подробнее, вместо того, чтобы писать “Нет.”
- Наибольшей точностью из всех типов, которые могут по умолчанию записываться в экспоненциальном виде, обладает
real
(double
). - У
real
точность равна 15–16 значащих цифр, поэтому в нижеприведённом коде не имеет смысла писать :f17 и больше. - String вместит число любой длины.
- Нижеприведённая функция не имеет недостатков, багов (например, неудаление лишних символов или удаление нужных), подходит для всех вещественных типов и достаточно хорошо справляется со своей задачей.
- У вас нет предложений по её улучшению (даже по улучшению названия).
ᅠ
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
.