To handle rational number without losing accuracy

2019-02-15 07:51发布

问题:

I want to use this rational number in computations without losing the accuracy of the picture in Matlab:

f = 359.0 + 16241/16250.0

I think storing, for instance by f = uint64(359.0 + 16241/16250.0) loses accuracy, seen as 360 in Matlab.

I think the best way to handle the thing is never to store the value but to store its factors like

% f = a + b/c
a = 359
b = 16241
c = 16250

and then doing computation by the variables a, b and c, and giving the result as a picture.

Is this a good way to maintain the accuracy?

回答1:

As you suggest, if you absolutely don't want to lose accuracy when storing a rational number, the best solution probably is to store the number in terms of its integer components.

Instead of your three components (f = a + b/c) you can reduce the reprentation to two components: f = n/d. Thus each rational number would be defined (and stored) as the two-component integer vector [n d]. For example, the number f in your example corresponds to n=5849991 and d=16250.

To simplify handling rational numbers stored this way, you could define a helper function which converts from the [n d] representation to n/d before applyling the desired operation:

useInteger = @(x, nd, fun) fun(x,double(nd(1))/double(nd(2)));

Then

>> x = sqrt(pi);
>> nd = int64([5849991 16250]);
>> useInteger(x, nd, @plus)
ans =
  361.7719
>> useInteger(x, nd, @times)
ans =
  638.0824

If you want to achieve arbitrarily high precision in computations, you should consider using variable-precision arithmetic (vpa) with string arguments. With that approach you get to specify how many digits you want:

>> vpa('sqrt(pi)*5849991/16250', 50)
ans =
638.08240465923757600307902117159072301901656248436


回答2:

Perhaps create a Rational class and define the needed operations (plus,minus,times,etc.). Start with something like this:

Rational.m

classdef Rational
    properties
        n;
        d;
    end
    methods
        function obj = Rational(n,d)
            GCD = gcd(n,d);
            obj.n = n./GCD;
            obj.d = d./GCD;
        end

        function d = dec(obj)
            d = double(obj.n)/double(obj.d);
        end

        % X .* Y
        function R = times(X,Y)
            chkxy(X,Y);
            if isnumeric(X),
                N = X .* Y.n; D = Y.d;
            elseif isnumeric(Y),
                N = X.n .* Y; D = X.d;
            else
                N = X.n .* Y.n; D = X.d .* Y.d;
            end
            R = Rational(N,D);
        end

        % X * Y
        function R = mtimes(X,Y)
            R = times(X,Y);
        end

        % X ./ Y
        function R = rdivide(X,Y)
            if isnumeric(Y),
                y = Rational(1,Y);
            else
                y = Rational(Y.d,Y.n);
            end
            R = times(X,y);
        end

        % X / Y
        function R = mrdivide(X,Y)
            R = rdivide(X,Y);
        end

        % X + Y
        function R = plus(X,Y)
            chkxy(X,Y);
            if isnumeric(X),
                N = X.*Y.d + Y.n; D = Y.d;
            elseif isnumeric(Y),
                N = Y.*X.d + X.n; D = X.d;
            else
                D = lcm(X.d,Y.d);
                N = sum([X.n Y.n].*(D./[X.d Y.d]));
            end
            R = Rational(N,D);
        end

        % X - Y
        function R = minus(X,Y)
            R = plus(X,-Y);
        end

        % -X
        function R = uminus(X)
            R = Rational(-X.n,X.d);
        end

        function chkxy(X,Y)
            if (~isa(X, 'Rational') && ~isnumeric(X)) || ...
                    (~isa(Y, 'Rational') && ~isnumeric(Y)),
                error('X and Y must be Rational or numeric.');
            end
        end
    end
end

Examples

Construct objects:

>> clear all % reset class definition
>> r1 = Rational(int64(1),int64(2))
r1 = 
  Rational with properties:

    n: 1
    d: 2
>> r2 = Rational(int64(3),int64(4))
r2 = 
  Rational with properties:

    n: 3
    d: 4

Add and subtract:

>> r1+r2
ans = 
  Rational with properties:

    n: 5
    d: 4
>> r1-r2
ans = 
  Rational with properties:

    n: -1
    d: 4

Multiply and divide:

>> r1*r2
ans = 
  Rational with properties:

    n: 3
    d: 8
>> r1/r2
ans = 
  Rational with properties:

    n: 2
    d: 3

Get decimal value:

>> r12 = r1/r2; % 2/3 ((1/2)/(3/4))
>> f = r12.dec
f =
    0.6667


回答3:

Extension to LuisMendo's answer

I got this as the error for your suggestion by py

>>> a = 638.08240465923757600307902117159072301901656248436059
>>> a
638.0824046592376             % do not know if Python is computing here with exact number
>>> b = 638.0824
>>> ave = abs(b+a)/2
>>> diff = abs(b-a)
>>> ave = abs(b+a)/2
>>> diff/ave
7.30193709165014e-09

which is more than the proposed error storing error above.

I run in WolframAlpha

x = sqrt(pi)
x*5849991/16250

and get

509.11609919757198016211937362635174599076143654820109

I am not sure if this is what you meant in your comment of your answer.



回答4:

Extension to chappjc's answer.

I have now

[B,T,F] = tfrwv(data1, 1:length(data1), length(data1)); % here F double
fs = Rational(uint64(5849991), uint64(16250));
t = 1/fs;
imagesc(T*t, F*fs, B); 

I run it

Error using  .* 
Integers can only be combined with integers of
the same class, or scalar doubles.

Error in  .*  (line 23)
                    N = X .* Y.n; D = Y.d;

Error in  *  (line 34)
                R = times(X,Y);

How can you multiply in this class the double with Rational?