标题: [数值计算] 【已解决】批处理的整数变量是如何解析存储的? [打印本页]
作者: buyiyang 时间: 2023-9-24 16:16 标题: 【已解决】批处理的整数变量是如何解析存储的?
本帖最后由 buyiyang 于 2023-9-26 09:18 编辑
批处理数字精度是32位,如果是补码存储,为什么会出现这种情况?- set /a n=-2147483648
- 无效数字。数字精确度限为 32 位。
复制代码
无法输入-2147483648,但却可以计算输出,- set /a n=-2147483647-1
- set /a n=2147483647+1
复制代码
都可以为变量n赋值为-2147483648,问题是为什么set /a n=-2147483648不能实现赋值?
作者: hlzj88 时间: 2023-9-24 17:06
可以输入 负数,你的问题是第一个代码框里 使用了 /a ,这是令set计算的用法。
作者: 老刘1号 时间: 2023-9-24 17:23
纯猜想,可能parser实现的时候不管有没有负号都是按正数读的,之后才转成负数
作者: Five66 时间: 2023-9-24 19:16
大概是
cmd数值范围是-2147483647~214748364,cmd在解析时,判断-2147483648超出范围了,出错
但是32位有符号范围是: -2147483648~214748364,不影响计算,能正常保存并输出
0有两个的,,-2147483648其实是负0,但是一般只使用正0
作者: 老刘1号 时间: 2023-9-24 20:50
回复 4# Five66
你这个结论非常不靠谱
从楼主的复制代码
测试样例可以看出来,INT_MAX+1是正常溢出到-2147483648也就是INT_MIN
32bit一共只能表示那么多数,哪有地方给你存两个零?
况且历史上也没有这种编码的先例
作者: Five66 时间: 2023-9-24 21:59
回复 5# 老刘1号
我是这样理解的- 32位有符号正整数2147483647原码为:01111111 11111111 11111111 11111111
- 加1后
- 结果为:10000000 00000000 00000000 00000000 (没有溢出,还是32位,只是计算后最高位变了)
- 因为最高位为1,输出时被解析为负数
-
- 而32位有符号整数中
- -2147483648的原码就是:10000000 00000000 00000000 00000000 (表示负0)
- 正0是:00000000 00000000 00000000 00000000 (32个0)
复制代码
作者: buyiyang 时间: 2023-9-24 22:52
回复 6# Five66
计算机算的是补码,10000000 00000000 00000000 00000000是-2147483648的补码而不是原码。
作者: buyiyang 时间: 2023-9-24 22:56
回复 3# 老刘1号
可能吧,我看了操作正数和负数的内存,似乎有点不同,但我不懂汇编。
set /a n=2147483647- jae 00000067
- je 00000024
- das
- popad
- and [esi+3D],ch
- xor dh,[ecx]
- xor al,37
- xor al,38
- xor esi,[esi]
- xor al,37
复制代码
set /a n=-2147483647- jae 00000067
- je 00000024
- das
- popad
- and [esi+3D],ch
- sub eax,37343132
- xor al,38
- xor esi,[esi]
- xor al,37
复制代码
作者: buyiyang 时间: 2023-9-24 23:01
回复 2# hlzj88
set n=-2147483648确实可以,然后可以操作set /a n=n+1,但我想问的是set n=-2147483648为什么不行。
作者: hlzj88 时间: 2023-9-24 23:25
回复 9# buyiyang
哎,理解了一半题目就发言了,知道,没想到cmd有最大值的限定。六楼讲的应当是对的,也没时间和方法去求证。
作者: Five66 时间: 2023-9-24 23:32
回复 7# buyiyang
说是说原码补码,其是我想说的是2进制值
作者: 老刘1号 时间: 2023-9-24 23:55
本帖最后由 老刘1号 于 2023-9-25 00:08 编辑
这种问题靠猜也没用
把我压箱底的NT4系统泄露源代码翻出来- int SetWork(n)
- struct cmdnode *n ;
- {
- TCHAR *tas ; /* Tokenized argument string */
- TCHAR *wptr ; /* Work pointer */
- int i ; /* Work variable */
-
- //
- // If extensions are enabled, things are different
- //
- if (fEnableExtensions) {
- tas = n->argptr;
- //
- // Find first non-blank argument.
- //
- if (tas != NULL)
- while (*tas && *tas <= SPACE)
- tas += 1;
-
- //
- // No arguments, same as old behavior. Display current
- // set of environment variables.
- //
- if (!tas || !*tas)
- return(DisplayEnv()) ;
-
- //
- // See if /A switch given. If so, let arithmetic
- // expression evaluator do the work.
- //
- if (!_tcsnicmp(tas, SetArithStr, 2))
- return SetArithWork(tas+2);
-
- //
- // See if first argument is quoted. If so, strip off
- // leading quote, spaces and trailing quote.
- //
- if (*tas == QUOTE) {
- tas += 1;
- while (*tas && *tas <= SPACE)
- tas += 1;
- wptr = _tcsrchr(tas, QUOTE);
- if (wptr)
- *wptr = NULLC;
- }
-
- //
- // Find the equal sign in the argument.
- //
- wptr = _tcschr(tas, EQ);
-
- //
- // If no equal sign, then assume argument is variable name
- // and user wants to see its value. Display it.
- //
- if (!wptr)
- return DisplayEnvVariable(tas);
-
- //
- // Found the equal sign, so left of equal sign is variable name
- // and right of equal sign is value. Dont allow user to set
- // a variable name that begins with an equal sign, since those
- // are reserved for drive current directories.
- //
- *wptr++ = NULLC;
- if (*wptr == EQ) {
- PutStdErr(MSG_BAD_SYNTAX, NOARGS);
- return(FAILURE) ;
- }
-
- return(SetEnvVar(tas, wptr, &CmdEnv)) ;
- }
-
- tas = TokStr(n->argptr, ONEQSTR, TS_WSPACE|TS_SDTOKENS) ;
- if (!*tas)
- return(DisplayEnv()) ;
-
- else {
- for (wptr = tas, i = 0 ; *wptr ; wptr += mystrlen(wptr)+1, i++)
- ;
- /* If too many parameters were given, the second parameter */
- /* wasn't an equal sign, or they didn't specify a string */
- /* return an error message. */
- if ( i > 3 || *(wptr = tas+mystrlen(tas)+1) != EQ ||
- !mystrlen(mystrcpy(tas, stripit(tas))) ) {
- /* M013 */ PutStdErr(MSG_BAD_SYNTAX, NOARGS);
- return(FAILURE) ;
-
- } else {
- return(SetEnvVar(tas, wptr+2, &CmdEnv)) ;
- }
- } ;
- }
复制代码
- /*** SetArithWork - set environment variable to value of arithmetic expression
- *
- * Purpose:
- * Set environment variable to value of arithmetic expression
- *
- * int SetArithWork(TCHAR *tas)
- *
- * Args:
- * tas - pointer to null terminated string of the form:
- *
- * VARNAME=expression
- *
- * Returns:
- * If valid expression, return SUCCESS otherwise FAILURE.
- *
- */
-
- int SetArithWork(TCHAR *tas)
- {
- TCHAR c, szResult[ MAX_PATH ];
- TCHAR *szOperator;
- TCHAR *wptr;
- DWORD i;
- BOOLEAN bUnaryOpPossible;
- int rc;
-
-
- //
- // If no input, declare an error
- //
- if (!tas || !tas) {
- PutStdErr(MSG_BAD_SYNTAX, NOARGS);
- return(FAILURE) ;
- }
-
- //
- // Now evaluate the expression. Syntax accepted:
- //
- // <expr>: '(' <expr> ')'
- // | <unary-op> <expr>
- // | <expr> <binary-op> <expr>
- // | <variable>
- // | <number>
- //
- // <unary-op>: '+' | '-' |
- // '~' | '!'
- //
- // <binary-op>: '+' | '-' | '*' | '/' | '%'
- // '|' | '&' | '^' | '=' | ','
- //
- // <number>: C-syntax (e.g. 16 or 0x10)
- // <variable>: Any environment variable. Dont need surround with % to get value
- //
- // Operators have same meaning and precedence as ANSI C. All arithmetic is
- // fixed, 32 bit arithmetic. No floating point.
- //
-
- //
- // Poor man's parser/evaluator with operand and operator stack.
- //
- iOperand = 0;
- iOperator = 0;
- bUnaryOpPossible = TRUE;
- rc = SUCCESS;
- do {
- //
- // Look at next non blank character
- //
- c = *tas;
- if (c <= SPACE || c == QUOTE) {
- if (*tas)
- tas += 1;
- }
- else
- if (_istdigit(c)) {
- //
- // Digit, must be numeric operand. Push it on operand stack
- //
- lOperands[ iOperand ].Value = _tcstol(tas, &tas, 0);
- lOperands[ iOperand ].Name = NULL;
- iOperand += 1;
-
- if (_istdigit(*tas) || _istalpha(*tas)) {
- rc = MSG_SET_A_INVALID_NUMBER;
- break;
- }
-
- //
- // Unary op not possible after a operand.
- //
- bUnaryOpPossible = FALSE;
- }
- else
- if (bUnaryOpPossible && (szOperator = _tcschr(szUnaryOps, c))) {
- //
- // If unary op possible and we have one, then push it
- // on the operator stack
- tas += 1;
- if (rc = DoArithOps( wUnaryOpCodes[szOperator - szUnaryOps] ))
- break;
- }
- else
- if (!bUnaryOpPossible && (szOperator = _tcschr(szOps, c))) {
- //
- // If we have a binary op, push it on the operator stack
- //
- tas += 1;
-
- if (c == L'<' || c == L'>') {
- if (*tas != c) {
- rc = MSG_SYNERR_GENL;
- break;
- }
- tas += 1;
- }
-
- if (*tas == EQ) {
- tas += 1;
- if (rc = DoArithOps( A_EQOP ))
- break;
-
- if (iOperand == 0) {
- rc = MSG_SET_A_MISSING_OPERAND;
- break;
- }
- lOperands[ iOperand ] = lOperands[ iOperand-1 ];
- iOperand += 1;
- }
-
- if (rc = DoArithOps( wOpCodes[szOperator - szOps] ))
- break;
-
- //
- // Unary op now possible.
- //
-
- if (c == RPOP) {
- bUnaryOpPossible = FALSE;
- }
- else {
- bUnaryOpPossible = TRUE;
- }
- }
- else
- if (!bUnaryOpPossible) {
- rc = MSG_SET_A_MISSING_OPERATOR;
- break;
- }
- else {
- //
- // Not a number or operator, must be a variable name. The
- // name must be terminated by a space or an operator.
- //
- wptr = tas;
- while (*tas &&
- *tas > SPACE &&
- !_tcschr(szUnaryOps, *tas) &&
- !_tcschr(szOps, *tas)
- )
- tas += 1;
-
- //
- // If no variable or variable too long, bail
- //
- if (wptr == tas) {
- rc = MSG_SET_A_MISSING_OPERAND;
- break;
- }
-
- lOperands[ iOperand ].Value = 0;
- lOperands[ iOperand ].Name = gmkstr((tas-wptr+1)*sizeof(TCHAR));
- if (lOperands[ iOperand ].Name == NULL) {
- rc = MSG_NO_MEMORY;
- break;
- }
-
- //
- // Have variable name. Push name on operand stack with a zero
- // value. Value will be fetch when this operand is popped from
- // operand stack.
- //
- _tcsncpy(lOperands[ iOperand ].Name, wptr, tas-wptr);
- lOperands[ iOperand ].Name[tas-wptr] = NULLC;
- iOperand += 1;
-
- //
- // Unary op not possible after a operand.
- //
- bUnaryOpPossible = FALSE;
- }
-
- } while (*tas);
-
- if (rc == SUCCESS) {
- //
- // Do any pending operators.
- //
- rc = DoArithOps( 0 );
-
- //
- // If operator stack non-empty or more than one value
- // on operand stack, then invalid expression
- //
- if (rc == SUCCESS && (iOperator || iOperand != 1)) {
- if (iOperator)
- rc = MSG_SET_A_MISMATCHED_PARENS;
- else
- rc = MSG_SET_A_MISSING_OPERAND;
- }
- }
-
- if (rc != SUCCESS)
- PutStdErr(rc, ONEARG, tas);
- else {
- //
- // Valid result, display if not in a batch script.
- //
- if (!CurBat)
- cmd_printf( TEXT("%d"), lOperands[ 0 ].Value ) ;
- }
-
- return rc;
- }
复制代码
- /*** DoArithOps - push operator on operator stack arithmetic expression
- *
- * Purpose:
- * Push an operator on the operator stack. Operator precedence handed here
- *
- * int DoArithOps(USHORT OpCode)
- *
- * Args:
- * OpCode - operator value. Precendence encoded in value, such that operators
- * with higher precedence are numerically larger
- *
- * Returns:
- * If valid expression, return SUCCESS otherwise FAILURE.
- *
- */
- int
- DoArithOps(
- USHORT OpCode
- )
- {
- USHORT op;
- LONG op1, op2, result;
- TCHAR szResult[ 32 ];
-
- //
- // Loop until we can push this operator onto the operator stack.
- // These means we have to pop off and any operators on the stack
- // that have a higher precedence than the new operator.
- //
- while (TRUE) {
- if (OpCode == A_ENDOP) {
- //
- // If new opcode is a right parenthesis, then all done if
- // the left parenthesis is on the top of the operator stack
- //
- if (iOperator != 0 && wOperators[iOperator-1] == A_BEGOP) {
- iOperator -= 1;
- OpCode = 0;
- break;
- }
-
- //
- // No left paren left, keep popping until we see it. Error
- // if nothing left to pop.
- //
- if (iOperator == 0)
- return MSG_SET_A_MISMATCHED_PARENS;
- }
- else
- if (OpCode == 0) {
- //
- // OpCode zero is end of expression. Pop everything off.
- //
- if (iOperator == 0)
- break;
- }
- else
- if (iOperator == 0 || OpCode > wOperators[iOperator-1] || OpCode == A_BEGOP) {
- //
- // Done if no more operators to process or
- // New operator has higher precedence than operator on top of stack
- // or new operator is a left parenthesis
- //
- break;
- }
-
- //
- // We need to pop and process (i.e. evaluate) the operator stack
- // If it is a two operand stack, then get the second argument from
- // the top of the operand stack. Done if not two operands on the
- // operand stack.
- //
- op = wOperators[--iOperator];
- if (op < A_NEGOP) {
- if (iOperand < 2)
- break;
- op2 = PopOperand();
- }
-
- //
- // Get the first operand from the top of the operand stack. Error
- // if not one there.
- //
- if (iOperand < 1)
- break;
- op1 = PopOperand();
-
-
- //
- // Evaluate the operator and push the result back on the operand stack
- //
- result = 0;
- switch( op ) {
- case A_LSHOP: result = op1 << op2; break;
- case A_RSHOP: result = op1 >> op2; break;
- case A_ADDOP: result = op1 + op2; break;
- case A_SUBOP: result = op1 - op2; break;
- case A_MULOP: result = op1 * op2; break;
- case A_DIVOP: if (op2 == 0) return MSG_SET_A_MISSING_OPERAND;
- result = op1 / op2; break;
- case A_MODOP: result = op1 % op2; break;
- case A_ANDOP: result = op1 & op2; break;
- case A_OROP: result = op1 | op2; break;
- case A_XOROP: result = op1 ^ op2; break;
- case A_NEGOP: result = - op1; break;
- case A_POSOP: result = + op1; break;
- case A_NOTOP: result = ~ op1; break;
- case A_SEPOP: result = op2; break;
- case A_EQOP: if (lOperands[iOperand].Name == NULL) return MSG_SET_A_BAD_ASSIGN;
- result = op2;
- //
- // Left handside has variable name, convert result
- // to text and store as value of variable.
- //
- _sntprintf( szResult, 32, TEXT("%d"), result ) ;
- if (SetEnvVar(lOperands[iOperand].Name, szResult, &CmdEnv) != SUCCESS)
- return GetLastError();
- break;
-
- default: break;
- }
-
- lOperands[iOperand].Value = result;
- lOperands[iOperand].Name = NULL;
- iOperand += 1;
- }
-
- //
- // If new operator code is not the end of the expression, push it onto the
- // operator stack.
- //
- if (OpCode != 0)
- wOperators[ iOperator++ ] = OpCode;
- return SUCCESS;
- }
复制代码
作者: Five66 时间: 2023-9-25 00:43
回复 12# 老刘1号
厉害了,看了半天,/a开关的执行算术运算的操作数op1, op2跟返回值result的类型都是LONG,不过那是以前的LONG,大概是现在的int32?至于解析的那部分,完全看不懂
作者: Five66 时间: 2023-9-25 01:02
查了下,_tcstol函数是将字符串转 32 位长整数 (long)
而后面的
if (_istdigit(*tas) || _istalpha(*tas)) {
rc = MSG_SET_A_INVALID_NUMBER;
break;
}
应该就是是在解析操作数时进行判断数字是否合法的
再后面的
case A_ADDOP: result = op1 + op2; break;
和
case A_SEPOP: result = op2; break;
是直接用解析出来的操作数进行计算和赋值的
作者: 老刘1号 时间: 2023-9-25 01:30
回复 14# Five66
哈哈,我猜对了
进行_tcstol的时候c已经是一个dight了,而c=*tas,那么也就是说tas指向的字符串是数字开头的,不存在负数的情况
下面计算函数中的单目运算符-也可以佐证我的观点
那么就很明确了,由于set的tokenize机制,-号被当做单目运算符而不是数字本身的一部分,而解析数字本身时由于正数的表示范围比负数少一个(0占了一个位置),所以绝对值最大的负数取相反数之后已经超出32位范围,所以转换失败;但这并不影响可以正常算出这个数字。
作者: 老刘1号 时间: 2023-9-25 01:41
补充一些细节,- long wcstol( const wchar_t *nptr, wchar_t **endptr, intbase);
-
- 释义:将字符串类型根据不同的基转化数字形式。
-
- 其中:[in] nptr 表示要进行扫描字符串指针
-
- [out] endptr 存储扫描后无法转化的剩余的字符串
复制代码
也就是说,读取后剩余部分会覆盖掉原来的tas
下面if的意思是,假如读取后的剩余部分的第一个字符还是数字或者字母的话,那么失败
例子:1000a执行后还剩下a,所以解析失败。
而2147483648由于超出32bit正数范围,解析后tas会指向8,读进去的数字是214748364,由于现在指向的还是一个数字,if命中,命令报错。
作者: czjt1234 时间: 2023-9-25 08:09
不明觉厉
作者: buyiyang 时间: 2023-9-25 17:10
本帖最后由 buyiyang 于 2023-9-25 19:15 编辑
回复 12# 老刘1号
大概明白了,算数运算的解析字符串表达式是逐字符的,-首先被解析,被解析为操作符了,然后后面的数字2147483648被解析为操作数,操作数字符串要转长整型,过程中由于溢出遇到错误tas会指向最后停止的位置,判断指向的是数字8就会报错。
十分感谢@老刘1号提供的源码!基于第二个源码我再详细理解一下解析过程,有几个重要的变量,bUnaryOpPossible来判断是否允许一元操作符出现,初始值为TRUE。然后c=*tas,c是TCHAR类型的,c那么就是tas字符串数组的第一个字符,然后判断c是空白、字母、数字、操作符还是变量名,空白字符删除,操作符压入操作符栈,变量名压入操作数栈,变量名存储在 lOperands 数组中的Name字段,计算表达式时从环境变量中获取其值,如果是数字就从对tas字符串进行_tcstol处理转成长整型,遇到错误就将指针指向最后停止的位置,去除tas字符串前面成功转成长整型的部分。每判断一个字符,都会tas += 1,去除tas字符串中前一个已经判断过的字符。进行do while循环中直到尾tas字符串\0。
比如,set /a n=1+1,对于字符串"n=11+1",首先指针指向n,c为'n',判断为变量名,然后指针移到=,判断为操作符,指针移到1,判断为数字,对"11+1"进行_tcstol处理,在+遇到错误,11转成长整型作为操作数,指针移到+,判断为操作符,指针移到1,判断为数字,对"1"进行_tcstol处理,1转成长整型作为操作数,指针移到\0,循环结束。
作者: buyiyang 时间: 2023-9-25 17:28
回复 15# 老刘1号
进行_tcstol的时候c已经是一个dight了,而c=*tas,那么也就是说tas指向的字符串是数字开头的
这个是如何看出来的?
作者: 老刘1号 时间: 2023-9-25 17:34
回复 19# buyiyang
见第二个文件的69和75行
作者: buyiyang 时间: 2023-9-25 17:44
回复 20# 老刘1号
这里从tas字符数组第一个地址开始判断每个字符是空白、字母、数字、操作符,如何得出tas指向的字符串是数字开头的?do循环中,c=*tas应该是tas字符数组的第一个字符,判断完后,然后tas += 1,再赋值c=*tas是tas字符数组的第二个字符。
作者: 老刘1号 时间: 2023-9-25 18:44
回复 21# buyiyang
可能我表述不太清晰,你可以理解为tas指向的子串(原字符串去掉了前面若干字符剩余的部分)
因为c的字符串本质上就是指向以0结尾的字节数组的指针嘛,指针+1了指向的那个字符串就变化了(相当于最前面的字符就没了)。
作者: buyiyang 时间: 2023-9-25 19:10
回复 22# 老刘1号
可能误解你的意思了,你的意思是:c=*tas,当能进行_tcstol的时候说明c是一个dight,此时tas指向的字符串是数字开头的。明白了,我以为你说一开始的tas
欢迎光临 批处理之家 (http://www.bathome.net/) |
Powered by Discuz! 7.2 |