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

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

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

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

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

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

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


person marvin2k    schedule 11.03.2010    source источник


Ответы (6)


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

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

На этой странице документации перечислены эквивалентные методы арифметических операций. Операция двоичного сложения 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 (используя ВСТРОЕННЫЙ функция) для выполнения добавления 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
person gnovice    schedule 12.03.2010
comment
+1 Эффективно, но страшно. Я думаю, что это должно сопровождаться оговоркой о том, что замена операторов на стандартные типы может плохо взаимодействовать с другими библиотеками или функциями Matlab, которые ожидают нормального + поведения Matlab на них. (Даже если MathWorks рекомендует это в документе.) Мощный для быстрых одноразовых действий, проблематичный для больших кодовых баз. - person Andrew Janke; 15.03.2010
comment
@Andrew: Вы делаете хорошее замечание. Я предполагаю, что такое решение используется только для конкретного инструмента, который создает OP, но может быть легко забыть вернуться к старому методу (ам) int32 после завершения работы инструмента. Чтобы использовать это только для определенного фрагмента кода, у меня была бы команда в начале этого кода, которая добавляла бы родительскую папку папки @int32 к пути, а затем удаляла ее из пути, когда она заканчивала работу. - person gnovice; 15.03.2010
comment
Интересно, могли бы вы использовать новые пространства имен MCOS для его защиты. Создайте пространство имен +cstylemath и поместите туда свои переопределения @int32 и код marvin2k, который хочет их вызывать. Это легко сделать, плюс это будет иметь лексическую область видимости, а не динамическую: другие функции по-прежнему будут видеть нормальное поведение @int32, даже если они вызываются из функций в +cstylemath. И не требуется попытка/поймать, чтобы очистить модификацию пути. - person Andrew Janke; 15.03.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. Много вызовов для небольших массивов могут замедлить работу, потому что вы много платите за вызов метода.

person Andrew Janke    schedule 15.03.2010
comment
+1: Немного больше работы, но классная идея. Я полагаю, что еще одним решением было бы взять перегруженные методы из моего решения и просто создать новый пользовательский объект, используя эти методы (как ваше решение, но без файлов MEX). - person gnovice; 15.03.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 ведет себя так, как задокументировано.

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

person Rex Kerr    schedule 11.03.2010

Я не эксперт по 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

person Scott C    schedule 09.01.2017