Как я получаю реальные целочисленные переполнения в MATLAB/октаве?

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

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

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

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

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

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

14
задан gnovice 12 March 2010 в 17:33
поделиться

5 ответов

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

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

На этой странице документации перечислены эквивалентные методы для арифметических операторов. Операция двоичного сложения A + B фактически обрабатывается функцией plus (A, B) . Следовательно, вы можете создать папку с именем @ int32 (помещенную в другую папку на вашем пути к MATLAB ) и поместить туда функцию plus.m , которая будет используется вместо встроенного метода для типов данных int32 .

Вот пример того, как вы можете разработать свою перегруженную функцию плюс , чтобы создать желаемое поведение переполнения / потери значимости:

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

Обратите внимание, что я вызываю встроенную функцию плюс ] (с использованием функции BUILTIN ) для выполнения сложения значений int32 , которые, как я знаю, не будут иметь проблем переполнения / потери значимости.Если бы я вместо этого выполнял целочисленное сложение с помощью операции A + B , это привело бы к рекурсивному вызову моего перегруженного метода плюс , что могло бы привести к дополнительным вычислительным затратам или (в наихудший сценарий, когда последняя строка была 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
18
ответ дан 1 December 2019 в 10:18
поделиться

Хм, да ...

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

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

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 ведет себя, как описано в документации.

1
ответ дан 1 December 2019 в 10:18
поделиться

Если 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
ответ дан 1 December 2019 в 10:18
поделиться

Если вы хотите получить числовые операции в стиле 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. Многие вызовы небольших массивов могут замедлить работу, потому что вы много платите накладные расходы на вызов каждого метода.

5
ответ дан 1 December 2019 в 10:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: