Как получить реальные целочисленные переполнения в MATLAB / Octave? - PullRequest
14 голосов
/ 11 марта 2010

Я работаю над инструментом проверки для некоторого VHDL-кода в MATLAB / Octave. Поэтому мне нужны типы данных, которые генерируют "реальные" переполнения:

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

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

Когда я создаю C-подобный пример, где переменная увеличивается до тех пор, пока она не станет меньше нуля, она вращается вечно:

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

Другим подходом, который я попробовал, была пользовательская подпрограмма «переполнения», которая вызывалась каждый раз после изменения числа. Этот подход был мучительно медленным, неосуществимым и не работающим во всех случаях вообще. Есть предложения?

Ответы [ 7 ]

18 голосов
/ 12 марта 2010

В MATLAB у вас есть один вариант - перегрузить методы, которые обрабатывают арифметические операции для целочисленных типов данных , создавая ваше собственное поведение с переполнением, которое приведет к "переносу" вокруг "целочисленного значения. Как указано в документации:

Вы можете определить или перегрузить свой собственный методы для int* (как можно для любого объект), поместив соответственно именованный метод в папке @int* внутри папка на вашем пути. Введите help datatypes для имен методов Вы можете перегрузить.

На этой странице документации перечислены эквивалентные методы для арифметических операторов. Операция двоичного сложения A+B фактически обрабатывается функцией plus(A,B). Поэтому вы можете создать папку с именем @int32 (помещенную в другую папку на вашем пути MATLAB ) и поместить туда функцию 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
5 голосов
/ 15 марта 2010

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

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

Вот MEX-файл, который выполняет операцию C "+" в массиве Matlab. Сделайте один из них для каждого оператора, для которого вы хотите работать в стиле 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 () и другие операции, которые вызывают подходящую функцию MEX в стиле C.

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

Затем оберните свои числа в @cnumeric, где вы хотите поведение int в стиле C, и сделайте с ними математику.

>> 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 голос
/ 12 марта 2010

Если 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, поскольку предполагается, что он должен округляться до целочисленных значений (а не усекаться).

1 голос
/ 11 марта 2010

Хм, да ...

На самом деле я смог решить проблему с помощью своего пользовательского "переполнения" - подпрограммы ... Теперь он работает мучительно медленно, но без неожиданного поведения! Моя ошибка заключалась в пропущенном раунде (), так как 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
1 голос
/ 11 марта 2010

Я запустил следующий фрагмент кода

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

с неожиданными результатами. Похоже, что для Matlab, intmax('int32')+1==intmax('int32'). Я запускаю 2010a на 64-битной Mac OS X.

Не уверен, что это как ответ, еще одно подтверждение того, что Matlab ведет себя нелогично. Однако документация для функции intmax() гласит:

Любое значение, превышающее значение, возвращаемое intmax, насыщается до значения intmax при приведении к 32-разрядному целому числу.

Так что я думаю, что Matlab ведет себя как задокументировано.

0 голосов
/ 09 января 2017

Я не эксперт по Java, но базовые классы Java, доступные в Matlab, должны разрешать обработку переполнений, как это делает C. Одно решение, которое я нашел, работает только для одного значения, но оно преобразует число в представление int16 (Short) или int32 (Integer). Вы должны сделать свою математику, используя 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

0 голосов
/ 11 марта 2010

Посмотрите на функцию intwarning.

...