【问题标题】:How do I get real integer overflows in MATLAB/Octave?如何在 MATLAB/Octave 中获得真正的整数溢出?
【发布时间】:2011-01-26 09:13:08
【问题描述】:

我正在为 MATLAB/Octave 中的一些 VHDL 代码开发验证工具。因此我需要产生“真实”溢出的数据类型:

intmax('int32') + 1
ans = -2147483648

稍后,如果我可以定义变量的位宽会很有帮助,但现在并不那么重要。

当我构建一个类似 C 的示例时,变量会不断增加直到小于零,它会永远旋转:

test = int32(2^30);
while (test > 0)
    test = test + int32(1);
end

我尝试的另一种方法是自定义“溢出”例程,每次更改数字后都会调用该例程。这种方法非常缓慢,不切实际,而且根本不适用于所有情况。有什么建议吗?

【问题讨论】:

    标签: matlab overflow integer-overflow


    【解决方案1】:

    在 MATLAB 中,您可以选择将处理 arithmetic operations 的方法重载为 integer data types,从而创建您自己的自定义溢出行为,从而导致整数值“回绕”。如文档中所述:

    您可以定义或重载您自己的 int* 的方法(你可以为任何 对象)通过适当地放置 @int* 文件夹中的命名方法 路径上的文件夹。键入 help datatypes 作为方法的名称 你可以超载。

    This page of the documentation 列出了算术运算符的等效方法。二进制加法运算A+B 实际上由函数plus(A,B) 处理。因此,您可以创建一个名为 @int32 的文件夹(放置在您的 MATLAB path 上的另一个文件夹中)并在其中放置一个函数 plus.m,该函数将用于代替 int32 数据类型的内置方法。

    这是一个示例,说明如何设计重载的 plus 函数以创建所需的上溢/下溢行为:

    function C = plus(A,B)
    %# NOTE: This code sample is designed to work for scalar values of
    %#       the inputs. If one or more of the inputs is non-scalar,
    %#       the code below will need to be vectorized to accommodate,
    %#       and error checking of the input sizes will be needed.
    
      if (A > 0) && (B > (intmax-A))  %# An overflow condition
    
        C = builtin('plus',intmin,...
                    B-(intmax-A)-1);  %# Wraps around to negative
    
      elseif (A < 0) && (B < (intmin-A))  %# An underflow condition
    
        C = builtin('plus',intmax,...
                    B-(intmin-A-1));  %# Wraps around to positive
    
      else
    
        C = builtin('plus',A,B);  %# No problems; call the built-in plus.m
    
      end
    
    end
    

    请注意,我调用了内置的plus 方法(使用BUILTIN 函数)来执行我知道不会遇到上溢/下溢问题的int32 值的相加。如果我改为使用操作A+B 执行整数加法,它将导致对我重载的plus 方法的递归调用,这可能导致额外的计算开销或(在最坏的情况下最后一行是C = A+B;) 无限递归。

    这是一个测试,展示了环绕式溢出行为的实际效果:

    >> A = int32(2147483642);  %# A value close to INTMAX
    >> for i = 1:10, A = A+1; disp(A); end
      2147483643
    
      2147483644
    
      2147483645
    
      2147483646
    
      2147483647   %# INTMAX
    
     -2147483648   %# INTMIN
    
     -2147483647
    
     -2147483646
    
     -2147483645
    
     -2147483644
    

    【讨论】:

    • +1 有效但可怕。我认为这应该附带一个警告,即替换标准类型上的运算符可能会与其他库或 Matlab 函数交互不良,这些库或 Matlab 函数期望 Matlab 对其具有正常的“+”行为。 (即使 MathWorks 确实在文档中推荐了它。)对于快速一次性使用非常强大,但对于大型代码库来说是有问题的。
    • @Andrew:你说得很好。我设想这种解决方案仅用于 OP 正在创建的特定工具,但一旦工具完成运行,很容易忘记恢复到旧的 int32 方法。要将其仅用于特定的代码段,我将在该代码的开头添加一个命令,将 @int32 文件夹的父文件夹添加到路径中,然后在完成运行时将其从路径中删除。跨度>
    • 我想知道您是否可以使用新的 MCOS 命名空间来保护它。创建一个 +cstylemath 命名空间并将你的 @int32 覆盖和想要调用它们的 marvin2k 的代码粘贴在那里。很容易做到,而且它是词法作用域而不是动态作用域:其他函数仍然会看到正常的@int32 行为,即使它们是从 +cstylemath 中的函数内部调用的。并且不需要 try/catch 来清理路径修改。
    【解决方案2】:

    如果您想获得 C 风格的数值运算,可以使用 MEX 函数直接调用 C 运算符,根据定义,它们将像 C 数据类型一样工作。

    这个方法比 gnovice 的重写工作很多,但它应该更好地集成到大型代码库中,并且比更改内置类型的定义更安全,所以我认为应该提到它为了完整性。

    这是一个 MEX 文件,它对 Matlab 数组执行 C“+”操作。为您希望 C 样式行为的每个运算符创建一个。

    /* c_plus.c - MEX function: C-style (not Matlab-style) "+" operation */
    
    #include "mex.h"
    #include "matrix.h"
    #include <stdio.h>
    
    void mexFunction(
                     int nlhs,       mxArray *plhs[],
                     int nrhs, const mxArray *prhs[]
                     )
    {
        mxArray     *out;
        /* In production code, input/output type and bounds checks would go here. */
        const mxArray     *a = prhs[0];
        const mxArray     *b = prhs[1];
        int         i, n;
        int *a_int32, *b_int32, *out_int32;
        short *a_int16, *b_int16, *out_int16;
    
        mxClassID datatype = mxGetClassID(a);
        int n_a = mxGetNumberOfElements(a);
        int n_b = mxGetNumberOfElements(b);
        int         a_is_scalar = n_a == 1;
        int         b_is_scalar = n_b == 1;
        n = n_a >= n_b ? n_a : n_b;
        out = mxCreateNumericArray(mxGetNumberOfDimensions(a), mxGetDimensions(a),
                datatype, mxIsComplex(a));
    
        switch (datatype) {
            case mxINT32_CLASS:
                a_int32 = (int*) mxGetData(a);
                b_int32 = (int*) mxGetData(b);
                out_int32 = (int*) mxGetData(out);
                for (i=0; i<n; i++) {
                    if (a_is_scalar) {
                        out_int32[i] = a_int32[i] + b_int32[i];
                    } else if (b_is_scalar) {
                        out_int32[i] = a_int32[i] + b_int32[0];
                    } else {
                        out_int32[i] = a_int32[i] + b_int32[i];
                    }
                }
                break;
            case mxINT16_CLASS:
                a_int16 = (short*) mxGetData(a);
                b_int16 = (short*) mxGetData(b);
                out_int16 = (short*) mxGetData(out);
                for (i=0; i<n; i++) {
                    if (a_is_scalar) {
                        out_int16[i] = a_int16[0] + b_int16[i];
                    } else if (b_is_scalar) {
                        out_int16[i] = a_int16[i] + b_int16[0];
                    } else {
                        out_int16[i] = a_int16[i] + b_int16[i];
                    }
                }
                break;
            /* Yes, you'd have to add a separate case for every numeric mxClassID... */
            /* In C++ you could do it with a template. */
            default:
                mexErrMsgTxt("Unsupported array type");
                break;
        }
    
        plhs[0] = out;
    }
    

    然后你必须弄清楚如何从你的 Matlab 代码中调用它。如果您正在编写所有代码,则可以在任何地方都调用“c_plus(a, b)”而不是“a + b”。或者,您可以创建自己的数字包装类,例如@cnumeric,在其字段中保存一个 Matlab 数值数组,并定义 plus() 和其他调用适当的 C 样式 MEX 函数的操作。

    classdef cnumeric
        properties
            x % the underlying Matlab numeric array
        end
        methods
            function obj = cnumeric(x)
                obj.x = x;
            end
    
            function out = plus(a,b)
                [a,b] = promote(a, b); % for convenience, and to mimic Matlab implicit promotion
                if ~isequal(class(a.x), class(b.x))
                    error('inputs must have same wrapped type');
                end
                out_x = c_plus(a.x, b.x);
                out = cnumeric(out_x);
            end
    
            % You'd have to define the math operations that you want normal
            % Matlab behavior on, too
            function out = minus(a,b)
                [a,b] = promote(a, b);
                out = cnumeric(a.x - b.x);
            end
    
            function display(obj)
                fprintf('%s = \ncnumeric: %s\n', inputname(1), num2str(obj.x));
            end
    
            function [a,b] = promote(a,b)
            %PROMOTE Implicit promotion of numeric to cnumeric and doubles to int
                if isnumeric(a); a = cnumeric(a); end
                if isnumeric(b); b = cnumeric(b); end
                if isinteger(a.x) && isa(b.x, 'double')
                    b.x = cast(b.x, class(a.x));
                end
                if isinteger(b.x) && isa(a.x, 'double')
                    a.x = cast(a.x, class(b.x));
                end
            end
        end
    
    end
    

    然后将您的数字包装在您想要 C 样式 int 行为的 @cnumeric 中并用它们进行数学运算。

    >> cnumeric(int32(intmax))
    ans = 
    cnumeric: 2147483647
    >> cnumeric(int32(intmax)) - 1
    ans = 
    cnumeric: 2147483646
    >> cnumeric(int32(intmax)) + 1
    ans = 
    cnumeric: -2147483648
    >> cnumeric(int16(intmax('int16')))
    ans = 
    cnumeric: 32767
    >> cnumeric(int16(intmax('int16'))) + 1
    ans = 
    cnumeric: -32768
    

    这是您的 C 风格溢出行为,与破坏原始 @int32 类型隔离。此外,您可以将 @cnumeric 对象传递给其他需要常规数字的函数,只要它们以多态方式处理其输入,它就会“工作”。

    性能警告:因为这是一个对象,所以 + 将具有较慢的方法分派速度而不是内置方法。如果您对大型数组的调用很少,这会很快,因为实际的数字运算在 C 中。对小型数组的大量调用可能会减慢速度,因为您为每个方法调用付出了很多开销。

    【讨论】:

    • +1:还有很多工作要做,但是一个很酷的想法。我想甚至另一种解决方案是从我的解决方案中获取重载方法,并使用这些方法创建一个新的用户定义对象(如您的解决方案,但没有 MEX 文件)。
    【解决方案3】:

    我运行了下面的 sn-p 代码

    test = int32(2^31-12);
    for i = 1:24
        test = test + int32(1)
    end
    

    结果出乎意料。看来,对于 Matlab,intmax('int32')+1==intmax('int32')。我在 64 位 Mac OS X 上运行 2010a。

    不确定这是一个答案,更多地确认 Matlab 的行为违反直觉。但是,intmax() 函数的文档指出:

    当转换为 32 位整数时,任何大于 intmax 返回值的值都会饱和到 intmax 值。

    所以我猜 Matlab 的行为与记录一致。

    【讨论】:

      【解决方案4】:

      嗯,是的……

      实际上,我能够用我的自定义“溢出”子程序解决问题......现在它运行缓慢,但没有意外行为!我的错误是缺少 round(),因为 Matlab/Octave 会引入小错误。

      但如果有人知道更快的解决方案,我很乐意尝试!

      function ret = overflow_sg(arg,bw)
      
          % remove possible rounding errors, and prepare returnvalue (if number is inside boundaries, nothing will happen)
          ret = round(arg);
      
          argsize = size(ret);
      
          for i = 1:argsize(1)
              for j = 1:argsize(2)
                  ret(i,j) = flow_sg(ret(i,j),bw);
              end
          end
      
      end%function
      
      %---
      
      function ret = flow_sg(arg,bw)
          ret = arg;
          while (ret < (-2^(bw-1)))
              ret = ret + 2^bw;
          end
      
          % Check for overflows:
          while (ret > (2^(bw-1)-1))
              ret = ret - 2^bw;
          end
      end%function
      

      【讨论】:

        【解决方案5】:

        如果 64 位足以不溢出,并且您需要很多这些,也许这样做:

        function ret = overflow_sg(arg,bw)
          mask = int64(0);
          for i=1:round(bw)
            mask = bitset(mask,i);
          end
          topbit = bitshift(int64(1),round(bw-1));
          subfrom = double(bitshift(topbit,1))
        
        
          ret = bitand( int64(arg) , mask );
          i = (ret >= topbit);
          ret(i) = int64(double(ret(i))-subfrom);
          if (bw<=32)
            ret = int32(ret);
          end
        end
        

        几乎所有事情都是作为矩阵计算完成的,很多事情都是用位完成的,而且所有事情都是一步完成的(没有 while 循环),所以它应该很快。如果要使用 rand 填充它,请减去 0.5,因为它假定它应该舍入为整数值(而不是截断)。

        【讨论】:

          【解决方案6】:

          我不是 Java 专家,但 Matlab 中可用的底层 Java 类应该允许像 C 那样处理溢出。我发现的一种解决方案仅适用于单个值,但它将数字转换为 int16(短)或 int32(整数)表示。您必须使用 Matlab double 进行数学运算,然后转换为 Java int16 或 int32,然后再转换回 Matlab double。不幸的是,Java 似乎不支持这种方式的无符号类型,只支持有符号。

          double(java.lang.Short(hex2dec('7FFF')))
          <br>ans = 32767
          
          double(java.lang.Short(hex2dec('7FFF')+1))
          <br>ans = -32768
          
          double(java.lang.Short(double(intmax('int16'))+1))
          <br>ans = -32768
          
          double(java.lang.Integer(hex2dec('7FFF')+1))
          <br>ans = 32768
          

          https://www.tutorialspoint.com/java/lang/java_lang_integer.htm

          【讨论】:

            猜你喜欢
            • 2011-02-01
            • 1970-01-01
            • 2011-01-08
            • 1970-01-01
            • 2021-11-29
            • 2016-05-11
            • 2011-11-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多