Как установить арифметическую точность в «заданных» переменных пакетных файлов?

1

Как мне установить точность арифметической операции SET на делениях?

например. SET /A t=3/4

Но это дает результат как ' 0', а не ' 0.75' (я хочу по крайней мере 2 десятичных знака)

Или есть какой-либо другой метод для таких арифметических операций (использующий кроме " set /A"), чтобы произойти в пакетных файлах?

Ирфан
источник
2
SET /Aработает только на целых числах. Таким образом, нет никакого способа 0.75использовать это
Nifle
@Nifle: опубликуйте это как ответ, прежде чем я это сделаю.
Гравитация

Ответы:

3

Если вы используете Microsoft CMD, то то, что сказал Nifle, применимо. Однако, если вы используете для его замены TCC / LE от JP Software , существует не только другой способ выполнения арифметики в командных сценариях, но он не ограничивается целочисленной арифметикой, а точность вычислений контролируется.

Конечно, TCC / LE поддерживает /Aопцию для SETкоманды. Но, как говорится в документации, можно также выполнять арифметику с @EVAL[]функцией переменной :

SET T=%@EVAL[3/4]

Точность вывода в %@EVAL[]расчете является управляемой опцией, которая устанавливается тремя способами:

JdeBP
источник
Спасибо .. я надеюсь, что смогу работать с этим гладко .. выглядит многообещающе.
Ирфан
5

SET / A работает только на целых числах. Таким образом, нет никакого способа получить 0,75, используя это.
И я не знаю другого способа сделать арифметику в пакетных файлах.

Nifle
источник
действительно? 2 отзыва? это даже не решение моей проблемы. это было лучше в комментариях между прочим .. Обходной путь JdeBP достаточно хорош (ниже).
Ирфан
1
Справедливости ради, и, как я отметил в своем ответе, сказанное Nifle применимо, если у него есть только Microsoft CMD. «Вы не можете делать то, что хотите, с командным интерпретатором, который у вас есть». является действительным ответом.
JdeBP
3

Возможно, вы не сможете делать то, что хотите, но вы можете приблизить желаемый результат, умножив и разделив на степени 10 и выполнив манипуляции со строками. Например, рассмотрим это divide.bat:

@echo off
set /a q=%1 * 1000 / %2
echo q = %q:~0,-3%.%q:~-3%

%variable_name:~start_pos,length%является обозначением подстроки переменной командной строки. Отрицательные числа относятся к длине строки (значение переменной); отсутствующая длина означает остаток строки.

Вот некоторые примеры результатов:

divide 8 4    ->    q = 2.000
divide 6 4    ->    q = 1.500
divide 3 4    ->    q = .750

В последнем примере ( ваш пример) на qсамом деле 750. Если вы хотите вычислить 75% чего-либо, либо

  • умножить на %q%и разделить на 1000, или
  • умножьте на %q%и помните, что результат равен 1000-кратному его истинному значению, и вам нужно использовать обозначение подстроки при его отображении.
Скотт
источник
1

Отсутствие начальных нулей в результате присваивания set / a приводит к сбою деления, когда результат меньше .100; остроумие,

разделить 3 40 -> д = 0,75

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

Вот решение (только для положительных чисел):

:divide

set /a q=%1 * 1000 / %2
set frac=00%q%
echo for x=%1, y=%2: q = %q:~0,-3%.%frac:~-3%

Другая проблема - отрицательные числа. Следующим последним символом может быть знак «-», который не должен появляться в дробной части. Самое простое решение для этого - удалить знак (если есть) и затем отобразить его перед десятичной строкой:

:divide

set /a q=%1 * 1000 / %2

set sgn=%q:~0,1%
if .%sgn%==.- (set q=%q:~1%) else (set sgn=)

set frac=00%q%
echo for x=%1, y=%2: q = %sgn%%q:~0,-3%.%frac:~-3%

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

:divide

set /a q=%1 * 10000 / %2

set sgn=%q:~0,1%
if .%sgn%==.- (set q=%q:~1%) else (set sgn=)

set /a q=(q+5)/10

set frac=00%q%

echo for x=%1, y=%2: q = %sgn%%q:~0,-3%.%frac:~-3%

Это последнее деление на 10 можно устранить, извлекая другой набор цифр для отображения:

:divide

set /a q=%1 * 10000 / %2

set sgn=%q:~0,1%
if .%sgn%==.- (set q=%q:~1%) else (set sgn=)

set /a q+=5

set frac=00%q%
echo for x=%1, y=%2: q = %sgn%%q:~0,-4%.%frac:~-4,-1%

Умножение на 10000 уменьшает область (т. Е. Диапазон параметра) функции. 32-битная арифметика cmd.exe может представлять значения от - (2 ^ 31) до (2 ^ 31) -1. Это от {-2147483648 до +2147483647}. Предварительное масштабирование на 10000 уменьшает это значение от {-214748 до +214748}.

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

Следующий код в основном извлечен из одного из моих проектов. Я переименовал переменные для общности и добавил код, чтобы разрешить отрицательные значения знаменателя. В моем проекте я хотел, чтобы отображаемое значение показывало ноль для числа единиц, а не начиналось с десятичной точки для значений меньше 1, поэтому я сгенерировал часть единиц арифметически, а не с использованием подстроки.
В случае, если вы предпочитаете не нулевую единицу для коэффициентов меньше единицы, я включил if defined no_zero_unitусловие, чтобы выбрать, какой формат отображать для небольших коэффициентов.

Этот код не защищен от переполнения. Хотя он может обрабатывать значения числителя или знаменателя до +/- 2147483647, он не может обрабатывать оба одновременно. Как отмечено в комментариях, переполнение может произойти, если min (abs (% 1), abs (% 2) -1) больше, чем 214748, или если abs (частное) больше, чем 214748.3642.

Если вы попытаетесь разделить -2147483648 на -1 (что невозможно в 32-разрядной арифметике со знаком), то при вычислении модуля он вылетит из cmd.exe с целочисленным переполнением. Кажется, хотя cmd.exe правильно обрабатывает исключение для деления, те же самые операнды искажают его для модуля.

Я не включал тесты для этих условий переполнения, хотя было бы довольно просто добавить их. Фактически мы могли бы удвоить диапазон отношения с помощью деления без знака для деления на 10. Сдвиг десятичной строки вправо не сработает, поскольку переполнение уже произошло. Если это решит проблему для кого-то, я буду рад выложить решение. Это добавляет только одну строку кода.

ПРИМЕЧАНИЕ: задержка расширения должна быть включена. Если вы удалите if defined no_zero_units ... else ...условное выражение и включите только код нужного вам формата (без скобок), вам не понадобится задержка с расширением.

Вот:

:grtdiv

rem delayedexpansion must be enabled before calling.

rem divides %1 by %2 and echos the quotient with 3 decimal places.

rem Constraints (violation of which yields invalid and possibly nonsensical output):
rem Max value of abs(quotient) is 214748.3642
rem Max value of min(abs(numerator), abs(denominator)-1) is 214748

rem By default, this subroutine echos a decimal string with the units
rem digit unconditionally present.  If you want no units digit for
rem values less than 1, then uncomment the following line:

rem set no_zero_units=defined

set /a qti = %1 / %2
set /a rem = %1 %% %2
set /a frc = rem*10000 / %2

rem Since abs(rem) can be any value up to min(abs(num), abs(den)-1),
rem frc calc can overflow if the lesser of these is greater than 214748.

rem Convert quotient integer and fraction parts to absolute value (either one may be zero,
rem so we can't just lop off the first character even if we know quotient is negative):

set qti=%qti:-=%
set frc=%frc:-=%

rem Get the correct sign:

set /a "sgn=(%1 ^ %2) & 0x80000000"
if %sgn%==0 (set sgn=+) else (set sgn=-)

rem frc is fraction in deci-milliseconds.

set /a qot3=(qti*10000+frc+5)/10

rem qot3 calc will overflow if abs(quotient) is greater than 214748.3642
rem qot3 is time in milliseconds rounded to nearest millisecond.
rem If value is zero, we clear sgn.

if %qot3%==0 (set sgn=)

rem Add 2 leading zeroes to value in milliseconds in case value is less than 100 ms;
rem Will use only the last 3 characters as the decimal fraction.

set frac=00%qot3%

if defined no_zero_units (

  @echo %1 / %2 = %sgn%%qot3:~0,-3%.%frac:~-3%

) else (

  set /a units=qot3/1000

  rem units is now the integer seconds part after rounding, and always has at least 1 digit.

  echo %1 / %2 = %sgn%!units!.%frac:~-3%
)
echo.

goto :eof

Чтобы устранить ограничение на частное, мы не можем просто поместить целую и дробную части в одну и ту же переменную. Единственная причина сделать это, когда остаток используется для вычисления дроби, состоит в том, что перенос от округления будет автоматически распространяться из дроби в целочисленную часть.

Для устранения ограничений на Числитель и Знаменатель требуются средства вычисления rem * 10000 / den без возможности переполнения при умножении.

Масштабирование как rem, так и den с помощью одного и того же фактора обеспечивает это за счет некоторой точности. Однако потеря точности имеет вычисляемую верхнюю границу и не имеет значения, когда предполагаемая конечная точность составляет 3 десятичных знака. Так как ОП попросил только «по крайней мере 2», это должно получиться хорошо.

Используемый здесь подход заключается в проверке rem по максимально допустимому значению и смещении вправо и rem и den до тех пор, пока rem не превысит этот предел. Как поясняется в комментариях, максимальная ошибка, возникающая в результате этого, появится в 5-м десятичном знаке.

:grtdv2

rem Great Divide version 2 by Dick Neubert 10/17/2017.

rem delayedexpansion must be enabled before calling.

rem Divides %1 by %2 and returns a string in %3 containing the quotient to 3 decimal places.
rem The sign is suppressed if quotient is zero, and there is always at least one digit to
rem the left of the decimal point.

rem This routine is accurate for any valid input values (except zero denominator).
rem The integer and fraction parts of the output are computed and stored separately
rem so the fraction digits don't reduce the range of the output value.  A scaling
rem step prevents overflow in the fraction calculation regardless of the remainder value.

rem Arguments:

rem %1 is numerator.
rem %2 is denominator.
rem %3 is name of variable (no percent signs) to receive the output value string.
rem    (output variable need not already exist at call)


rem Convert numerator and denominator to absolute values:

set num=%1
set den=%2
set num=%num:-=%
set den=%den:-=%

set /a qti = %num% / %den%
set /a rem = %num% %% %den%

rem echo remainder was %rem%.

rem Scale rem & den if necessary to prevent overflow when multiplying rem by 10000:

if %rem% gtr 214748 (

 :sclfrc
  set /a "den >>=1"
  set /a "rem >>=1"
  if !rem! gtr 214748 goto :sclfrc
)

rem echo remainder after scaling =%rem%.

rem This scaling reduces the precision of qtf to no worse than 1 part in (final value of shifted rem).
rem Value of rem after last shift is not less than 214748/2, or 107374.  den gets shifted along with it,
rem but den is always greater than rem before shifting (they can be equal after shifting).
rem The maximum change in rem/den is therefore not more than 1/107374 of the displayed fraction part,
rem or 0.999/107374 = 0.0000093, or about 1 percent of an LSD.

set /a qtf = rem*10000 / den

set /a qtf=(qtf+5)/10

if %qtf% geq 1000 set /a qti += 1

rem qtf is now the fraction part rounded, and qti is the integer part,
rem adjusted if rounding produced a carry or if rem==den after scaling.
rem Note that we'll only be keeping at most the 3 LSD's of qtf, so the 1000's digit will be discarded.

rem Get the correct sign:

set /a "sgn=(%1 ^ %2) & 0x80000000"
if %sgn%==0 (set sgn=+) else (set sgn=-)

rem If quotient is zero, we clear sgn.

if %qti%==0 if %qtf%==0 (set sgn=)

rem Pad fraction part with 2 leading zeroes in case value is less than 100 (.100);
rem Will use only the last 3 characters as the decimal fraction.

set qtf=00%qtf%

rem Assemble and return output string:

set %3=%sgn%%qti%.%qtf:~-3%

goto :eof

Расширить этот алгоритм до 4 отображаемых десятичных цифр с округлением было бы нереально. Множитель становится равным 100000, поэтому максимальное значение остатка после масштабирования уменьшается в 10 раз. Таким образом, точность вычисления ухудшается в 10 раз, в то время как отображаемая точность увеличивается на тот же коэффициент, и теперь эти два значения встречаются в последний раз. отображаемая цифра. Даже использование арифметики без знака для деления на 10 при округлении только улучшит это в 2 раза - недостаточно для корректировки результата. Однако если бы округление не применялось, множитель все равно был бы равен 10000, а точность была бы такой же, как в этой версии, но не округлялась.

DickN
источник