[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[数值计算] 【已解决】批处理的整数变量是如何解析存储的?

本帖最后由 buyiyang 于 2023-9-26 09:18 编辑

批处理数字精度是32位,如果是补码存储,为什么会出现这种情况?
  1. set /a n=-2147483648
  2. 无效数字。数字精确度限为 32 位。
复制代码
无法输入-2147483648,但却可以计算输出,
  1. set /a n=-2147483647-1
  2. set /a n=2147483647+1
复制代码
都可以为变量n赋值为-2147483648,问题是为什么set /a n=-2147483648不能实现赋值?
1

评分人数

    • Batcher: 感谢给帖子标题标注[已解决]字样PB + 2

可以输入 负数,你的问题是第一个代码框里 使用了  /a ,这是令set计算的用法。
1

评分人数

目的,学习批处理

TOP

纯猜想,可能parser实现的时候不管有没有负号都是按正数读的,之后才转成负数

TOP

大概是
cmd数值范围是-2147483647~214748364,cmd在解析时,判断-2147483648超出范围了,出错
但是32位有符号范围是: -2147483648~214748364,不影响计算,能正常保存并输出
0有两个的,,-2147483648其实是负0,但是一般只使用正0

TOP

回复 4# Five66


    你这个结论非常不靠谱
从楼主的
  1. set /a n=2147483647+1
复制代码
测试样例可以看出来,INT_MAX+1是正常溢出到-2147483648也就是INT_MIN
32bit一共只能表示那么多数,哪有地方给你存两个零?
况且历史上也没有这种编码的先例

TOP

回复 5# 老刘1号


我是这样理解的
  1. 32位有符号正整数2147483647原码为:01111111 11111111 11111111 11111111
  2. 加1后
  3. 结果为:10000000  00000000  00000000  00000000 (没有溢出,还是32位,只是计算后最高位变了)
  4. 因为最高位为1,输出时被解析为负数
  5. 而32位有符号整数中
  6. -2147483648的原码就是:10000000  00000000  00000000  00000000  (表示负0)
  7. 正0是:00000000  00000000  00000000  00000000 (32个0)
复制代码

TOP

回复 6# Five66


    计算机算的是补码,10000000  00000000  00000000  00000000是-2147483648的补码而不是原码。

TOP

回复 3# 老刘1号

可能吧,我看了操作正数和负数的内存,似乎有点不同,但我不懂汇编。
set /a n=2147483647
  1. jae 00000067
  2. je 00000024
  3. das
  4. popad
  5. and [esi+3D],ch
  6. xor dh,[ecx]
  7. xor al,37
  8. xor al,38
  9. xor esi,[esi]
  10. xor al,37
复制代码
set /a n=-2147483647
  1. jae 00000067
  2. je 00000024
  3. das
  4. popad
  5. and [esi+3D],ch
  6. sub eax,37343132
  7. xor al,38
  8. xor esi,[esi]
  9. xor al,37
复制代码

TOP

回复 2# hlzj88


    set n=-2147483648确实可以,然后可以操作set /a n=n+1,但我想问的是set n=-2147483648为什么不行。

TOP

回复 9# buyiyang
哎,理解了一半题目就发言了,知道,没想到cmd有最大值的限定。六楼讲的应当是对的,也没时间和方法去求证。
目的,学习批处理

TOP

回复 7# buyiyang


    说是说原码补码,其是我想说的是2进制值

TOP

本帖最后由 老刘1号 于 2023-9-25 00:08 编辑

这种问题靠猜也没用
把我压箱底的NT4系统泄露源代码翻出来
  1. int SetWork(n)
  2. struct cmdnode *n ;
  3. {
  4.         TCHAR *tas ;    /* Tokenized argument string    */
  5.         TCHAR *wptr ;   /* Work pointer                 */
  6.         int i ;                 /* Work variable                */
  7.         //
  8.         // If extensions are enabled, things are different
  9.         //
  10.         if (fEnableExtensions) {
  11.             tas = n->argptr;
  12.             //
  13.             // Find first non-blank argument.
  14.             //
  15.             if (tas != NULL)
  16.             while (*tas && *tas <= SPACE)
  17.                 tas += 1;
  18.             //
  19.             // No arguments, same as old behavior.  Display current
  20.             // set of environment variables.
  21.             //
  22.             if (!tas || !*tas)
  23.                 return(DisplayEnv()) ;
  24.             //
  25.             // See if /A switch given.  If so, let arithmetic
  26.             // expression evaluator do the work.
  27.             //
  28.             if (!_tcsnicmp(tas, SetArithStr, 2))
  29.                 return SetArithWork(tas+2);
  30.             //
  31.             // See if first argument is quoted.  If so, strip off
  32.             // leading quote, spaces and trailing quote.
  33.             //
  34.             if (*tas == QUOTE) {
  35.                 tas += 1;
  36.                 while (*tas && *tas <= SPACE)
  37.                     tas += 1;
  38.                 wptr = _tcsrchr(tas, QUOTE);
  39.                 if (wptr)
  40.                     *wptr = NULLC;
  41.             }
  42.             //
  43.             // Find the equal sign in the argument.
  44.             //
  45.             wptr = _tcschr(tas, EQ);
  46.             //
  47.             // If no equal sign, then assume argument is variable name
  48.             // and user wants to see its value.  Display it.
  49.             //
  50.             if (!wptr)
  51.                 return DisplayEnvVariable(tas);
  52.             //
  53.             // Found the equal sign, so left of equal sign is variable name
  54.             // and right of equal sign is value.  Dont allow user to set
  55.             // a variable name that begins with an equal sign, since those
  56.             // are reserved for drive current directories.
  57.             //
  58.             *wptr++ = NULLC;
  59.             if (*wptr == EQ) {
  60.                 PutStdErr(MSG_BAD_SYNTAX, NOARGS);
  61.                 return(FAILURE) ;
  62.             }
  63.             return(SetEnvVar(tas, wptr, &CmdEnv)) ;
  64.         }
  65.         tas = TokStr(n->argptr, ONEQSTR, TS_WSPACE|TS_SDTOKENS) ;
  66.         if (!*tas)
  67.                 return(DisplayEnv()) ;
  68.         else {
  69.                 for (wptr = tas, i = 0 ; *wptr ; wptr += mystrlen(wptr)+1, i++)
  70.                         ;
  71.                 /* If too many parameters were given, the second parameter */
  72.                 /* wasn't an equal sign, or they didn't specify a string   */
  73.                 /* return an error message.                                */
  74.                 if ( i > 3 || *(wptr = tas+mystrlen(tas)+1) != EQ ||
  75.                     !mystrlen(mystrcpy(tas, stripit(tas))) ) {
  76. /* M013 */              PutStdErr(MSG_BAD_SYNTAX, NOARGS);
  77.                         return(FAILURE) ;
  78.                 } else {
  79.                         return(SetEnvVar(tas, wptr+2, &CmdEnv)) ;
  80.                 }
  81.         } ;
  82. }
复制代码
  1. /***    SetArithWork - set environment variable to value of arithmetic expression
  2. *
  3. *  Purpose:
  4. *      Set environment variable to value of arithmetic expression
  5. *
  6. *  int SetArithWork(TCHAR *tas)
  7. *
  8. *  Args:
  9. *      tas - pointer to null terminated string of the form:
  10. *
  11. *          VARNAME=expression
  12. *
  13. *  Returns:
  14. *      If valid expression, return SUCCESS otherwise FAILURE.
  15. *
  16. */
  17. int SetArithWork(TCHAR *tas)
  18. {
  19.     TCHAR c, szResult[ MAX_PATH ];
  20.     TCHAR *szOperator;
  21.     TCHAR *wptr;
  22.     DWORD i;
  23.     BOOLEAN bUnaryOpPossible;
  24.     int rc;
  25.     //
  26.     // If no input, declare an error
  27.     //
  28.     if (!tas || !tas) {
  29.         PutStdErr(MSG_BAD_SYNTAX, NOARGS);
  30.         return(FAILURE) ;
  31.     }
  32.     //
  33.     // Now evaluate the expression.  Syntax accepted:
  34.     //
  35.     //  <expr>:     '(' <expr> ')'
  36.     //            | <unary-op> <expr>
  37.     //            | <expr> <binary-op> <expr>
  38.     //            | <variable>
  39.     //            | <number>
  40.     //
  41.     //  <unary-op>:  '+' | '-' |
  42.     //               '~' | '!'
  43.     //
  44.     //  <binary-op>: '+' | '-' | '*' | '/' | '%'
  45.     //               '|' | '&' | '^' | '=' | ','
  46.     //
  47.     //  <number>:   C-syntax (e.g. 16 or 0x10)
  48.     //  <variable>: Any environment variable.  Dont need surround with % to get value
  49.     //
  50.     //  Operators have same meaning and precedence as ANSI C.  All arithmetic is
  51.     //  fixed, 32 bit arithmetic.  No floating point.
  52.     //
  53.     //
  54.     // Poor man's parser/evaluator with operand and operator stack.
  55.     //
  56.     iOperand = 0;
  57.     iOperator = 0;
  58.     bUnaryOpPossible = TRUE;
  59.     rc = SUCCESS;
  60.     do {
  61.         //
  62.         // Look at next non blank character
  63.         //
  64.         c = *tas;
  65.         if (c <= SPACE || c == QUOTE) {
  66.             if (*tas)
  67.                 tas += 1;
  68.         }
  69.         else
  70.         if (_istdigit(c)) {
  71.             //
  72.             // Digit, must be numeric operand.  Push it on operand stack
  73.             //
  74.             lOperands[ iOperand ].Value = _tcstol(tas, &tas, 0);
  75.             lOperands[ iOperand ].Name = NULL;
  76.             iOperand += 1;
  77.             if (_istdigit(*tas) || _istalpha(*tas)) {
  78.                 rc = MSG_SET_A_INVALID_NUMBER;
  79.                 break;
  80.             }
  81.             //
  82.             // Unary op not possible after a operand.
  83.             //
  84.             bUnaryOpPossible = FALSE;
  85.         }
  86.         else
  87.         if (bUnaryOpPossible && (szOperator = _tcschr(szUnaryOps, c))) {
  88.             //
  89.             // If unary op possible and we have one, then push it
  90.             // on the operator stack
  91.             tas += 1;
  92.             if (rc = DoArithOps( wUnaryOpCodes[szOperator - szUnaryOps] ))
  93.                 break;
  94.         }
  95.         else
  96.         if (!bUnaryOpPossible && (szOperator = _tcschr(szOps, c))) {
  97.             //
  98.             // If we have a binary op, push it on the operator stack
  99.             //
  100.             tas += 1;
  101.             if (c == L'<' || c == L'>') {
  102.                 if (*tas != c) {
  103.                     rc = MSG_SYNERR_GENL;
  104.                     break;
  105.                 }
  106.                 tas += 1;
  107.             }
  108.             if (*tas == EQ) {
  109.                 tas += 1;
  110.                 if (rc = DoArithOps( A_EQOP ))
  111.                     break;
  112.                 if (iOperand == 0) {
  113.                     rc = MSG_SET_A_MISSING_OPERAND;
  114.                     break;
  115.                 }
  116.                 lOperands[ iOperand ] = lOperands[ iOperand-1 ];
  117.                 iOperand += 1;
  118.             }
  119.             if (rc = DoArithOps( wOpCodes[szOperator - szOps] ))
  120.                 break;
  121.             //
  122.             // Unary op now possible.
  123.             //
  124.             if (c == RPOP) {
  125.                 bUnaryOpPossible = FALSE;
  126.             }
  127.             else {
  128.                 bUnaryOpPossible = TRUE;
  129.             }
  130.         }
  131.         else
  132.         if (!bUnaryOpPossible) {
  133.             rc = MSG_SET_A_MISSING_OPERATOR;
  134.             break;
  135.         }
  136.         else {
  137.             //
  138.             // Not a number or operator, must be a variable name.  The
  139.             // name must be terminated by a space or an operator.
  140.             //
  141.             wptr = tas;
  142.             while (*tas &&
  143.                    *tas > SPACE &&
  144.                    !_tcschr(szUnaryOps, *tas) &&
  145.                    !_tcschr(szOps, *tas)
  146.                   )
  147.                 tas += 1;
  148.             //
  149.             // If no variable or variable too long, bail
  150.             //
  151.             if (wptr == tas) {
  152.                 rc = MSG_SET_A_MISSING_OPERAND;
  153.                 break;
  154.             }
  155.             lOperands[ iOperand ].Value = 0;
  156.             lOperands[ iOperand ].Name = gmkstr((tas-wptr+1)*sizeof(TCHAR));
  157.             if (lOperands[ iOperand ].Name == NULL) {
  158.                 rc = MSG_NO_MEMORY;
  159.                 break;
  160.             }
  161.             //
  162.             // Have variable name.  Push name on operand stack with a zero
  163.             // value.  Value will be fetch when this operand is popped from
  164.             // operand stack.
  165.             //
  166.             _tcsncpy(lOperands[ iOperand ].Name, wptr, tas-wptr);
  167.             lOperands[ iOperand ].Name[tas-wptr] = NULLC;
  168.             iOperand += 1;
  169.             //
  170.             // Unary op not possible after a operand.
  171.             //
  172.             bUnaryOpPossible = FALSE;
  173.         }
  174.     } while (*tas);
  175.     if (rc == SUCCESS) {
  176.         //
  177.         // Do any pending operators.
  178.         //
  179.         rc = DoArithOps( 0 );
  180.         //
  181.         // If operator stack non-empty or more than one value
  182.         // on operand stack, then invalid expression
  183.         //
  184.         if (rc == SUCCESS && (iOperator || iOperand != 1)) {
  185.             if (iOperator)
  186.                 rc = MSG_SET_A_MISMATCHED_PARENS;
  187.             else
  188.                 rc = MSG_SET_A_MISSING_OPERAND;
  189.         }
  190.     }
  191.     if (rc != SUCCESS)
  192.         PutStdErr(rc, ONEARG, tas);
  193.     else {
  194.         //
  195.         // Valid result, display if not in a batch script.
  196.         //
  197.         if (!CurBat)
  198.             cmd_printf( TEXT("%d"), lOperands[ 0 ].Value ) ;
  199.     }
  200.     return rc;
  201. }
复制代码
  1. /***    DoArithOps - push operator on operator stack      arithmetic expression
  2. *
  3. *  Purpose:
  4. *      Push an operator on the operator stack.  Operator precedence handed here
  5. *
  6. *  int DoArithOps(USHORT OpCode)
  7. *
  8. *  Args:
  9. *      OpCode - operator value.  Precendence encoded in value, such that operators
  10. *          with higher precedence are numerically larger
  11. *
  12. *  Returns:
  13. *      If valid expression, return SUCCESS otherwise FAILURE.
  14. *
  15. */
  16. int
  17. DoArithOps(
  18.     USHORT OpCode
  19.     )
  20. {
  21.     USHORT op;
  22.     LONG op1, op2, result;
  23.     TCHAR szResult[ 32 ];
  24.     //
  25.     // Loop until we can push this operator onto the operator stack.
  26.     // These means we have to pop off and any operators on the stack
  27.     // that have a higher precedence than the new operator.
  28.     //
  29.     while (TRUE) {
  30.         if (OpCode == A_ENDOP) {
  31.             //
  32.             // If new opcode is a right parenthesis, then all done if
  33.             // the left parenthesis is on the top of the operator stack
  34.             //
  35.             if (iOperator != 0 && wOperators[iOperator-1] == A_BEGOP) {
  36.                 iOperator -= 1;
  37.                 OpCode = 0;
  38.                 break;
  39.             }
  40.             //
  41.             // No left paren left, keep popping until we see it. Error
  42.             // if nothing left to pop.
  43.             //
  44.             if (iOperator == 0)
  45.                 return MSG_SET_A_MISMATCHED_PARENS;
  46.         }
  47.         else
  48.         if (OpCode == 0) {
  49.             //
  50.             // OpCode zero is end of expression.  Pop everything off.
  51.             //
  52.             if (iOperator == 0)
  53.                 break;
  54.             }
  55.         else
  56.         if (iOperator == 0 || OpCode > wOperators[iOperator-1] || OpCode == A_BEGOP) {
  57.             //
  58.             // Done if no more operators to process or
  59.             // New operator has higher precedence than operator on top of stack
  60.             // or new operator is a left parenthesis
  61.             //
  62.             break;
  63.         }
  64.         //
  65.         // We need to pop and process (i.e. evaluate) the operator stack
  66.         // If it is a two operand stack, then get the second argument from
  67.         // the top of the operand stack.  Done if not two operands on the
  68.         // operand stack.
  69.         //
  70.         op = wOperators[--iOperator];
  71.         if (op < A_NEGOP) {
  72.             if (iOperand < 2)
  73.                 break;
  74.             op2 = PopOperand();
  75.         }
  76.         //
  77.         // Get the first operand from the top of the operand stack.  Error
  78.         // if not one there.
  79.         //
  80.         if (iOperand < 1)
  81.             break;
  82.         op1 = PopOperand();
  83.         //
  84.         // Evaluate the operator and push the result back on the operand stack
  85.         //
  86.         result = 0;
  87.         switch( op ) {
  88.             case A_LSHOP: result = op1 << op2;  break;
  89.             case A_RSHOP: result = op1 >> op2;  break;
  90.             case A_ADDOP: result = op1 + op2;   break;
  91.             case A_SUBOP: result = op1 - op2;   break;
  92.             case A_MULOP: result = op1 * op2;   break;
  93.             case A_DIVOP: if (op2 == 0) return MSG_SET_A_MISSING_OPERAND;
  94.                           result = op1 / op2;   break;
  95.             case A_MODOP: result = op1 % op2;   break;
  96.             case A_ANDOP: result = op1 & op2;   break;
  97.             case A_OROP:  result = op1 | op2;   break;
  98.             case A_XOROP: result = op1 ^ op2;   break;
  99.             case A_NEGOP: result = - op1;       break;
  100.             case A_POSOP: result = + op1;       break;
  101.             case A_NOTOP: result = ~ op1;       break;
  102.             case A_SEPOP: result = op2;         break;
  103.             case A_EQOP:  if (lOperands[iOperand].Name == NULL) return MSG_SET_A_BAD_ASSIGN;
  104.                           result = op2;
  105.                           //
  106.                           // Left handside has variable name, convert result
  107.                           // to text and store as value of variable.
  108.                           //
  109.                           _sntprintf( szResult, 32, TEXT("%d"), result ) ;
  110.                           if (SetEnvVar(lOperands[iOperand].Name, szResult, &CmdEnv) != SUCCESS)
  111.                               return GetLastError();
  112.                           break;
  113.             default:    break;
  114.         }
  115.         lOperands[iOperand].Value = result;
  116.         lOperands[iOperand].Name = NULL;
  117.         iOperand += 1;
  118.     }
  119.     //
  120.     // If new operator code is not the end of the expression, push it onto the
  121.     // operator stack.
  122.     //
  123.     if (OpCode != 0)
  124.         wOperators[ iOperator++ ] = OpCode;
  125.     return SUCCESS;
  126. }
复制代码
1

评分人数

TOP

回复 12# 老刘1号


    厉害了,看了半天,/a开关的执行算术运算的操作数op1, op2跟返回值result的类型都是LONG,不过那是以前的LONG,大概是现在的int32?至于解析的那部分,完全看不懂

TOP

查了下,_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;
是直接用解析出来的操作数进行计算和赋值的

TOP

回复 14# Five66


    哈哈,我猜对了
进行_tcstol的时候c已经是一个dight了,而c=*tas,那么也就是说tas指向的字符串是数字开头的,不存在负数的情况
下面计算函数中的单目运算符-也可以佐证我的观点
那么就很明确了,由于set的tokenize机制,-号被当做单目运算符而不是数字本身的一部分,而解析数字本身时由于正数的表示范围比负数少一个(0占了一个位置),所以绝对值最大的负数取相反数之后已经超出32位范围,所以转换失败;但这并不影响可以正常算出这个数字。

TOP

返回列表