2

I'm trying to convert a base-10 integer k into a base-q integer, but not in the standard way. Firstly, I'd like my result to be a vectors (or a string 'a,b,c,...' so that it can be converted to a vector, but not 'abc...'). Most importantly, I'd like each 'digit' to be in base-10. As an example, suppose I have the number 23 (in base-10) and I want to convert it to base-12. This would be 1B in the standard 1,...,9,A,B notation; however, I want it to come out as [1, 11]. I'm only interested in numbers k with 0 \le k \le n^q - 1, where n is fixed in advance.

Put another way, I wish to find coefficients a(r) such that k = \sum_{r=0}^{n-1} a(r) q^r where each a(r) is in base-10. (Note that 0 \le a(r) \le q-1.)

I know I could do this with a for-loop -- struggling to get the exact formula at the moment! -- but I want to do it vectorised, or with a fast internal function.

However, I want to be able to take n to be large, so would prefer a faster way than this. (Of course, I could change this to a parfor-loop or do it on the GPU; these aren't practical for my current situation, so I'd prefer a more direct version.)

I've looked at stuff like dec2base, num2str, str2num, base2dec and so on, but with no luck. Any suggestion would be most appreciated.

Regarding speed and space, any preallocation for integers in the range [0, q-1] or similar would also be good.

To be clear, I am looking for an algorithm that works for any q and n, converting any number in the range [0,q^n - 1].

Sam OT
  • 420
  • 1
  • 4
  • 19
  • `floor`, `^`, and `/` are vectorized already. The loop isn't necessary. – sco1 Nov 18 '16 at 17:58
  • Ah, of course they are! Of course, my for-loop is rubbish and doesn't provide what I want at all =P -- let me just change that... – Sam OT Nov 18 '16 at 18:01

2 Answers2

4

You can use dec2base and replace the characters by numbers:

x = 23;
b = 12;
[~, result] = ismember(dec2base(x,b), ['0':'9' 'A':'Z']);
result = result -1;

gives

>> result
result =
     1    11

This works for base up to 36 only, due to dec2base limitations.


For any base (possibly above 36) you need to do the conversion manually. I once wrote a base2base function to do that (it's essentially long division). The number should be input as a vector of digits in the origin base, so you need dec2base(...,10) first. For example:

x = 125;
b = 6;
result = base2base(dec2base(x,10), '0':'9', b); % origin nunber, origin base, target base

gives

result =
     3     2     5

Or if you need to specify the number of digits:

x = 125;
b = 6;
d = 5;
result = base2base(dec2base(x,10), '0':'9', b, d)
result =
     0     0     3     2     5

EDIT (August 15, 2017): Corrected two bugs: handling of input consisting of all "zeros" (thanks to @Sanchises for noticing), and properly left-padding the output with "zeros" if needed.

function Z = base2base(varargin)
% Three inputs: origin array, origin base, target base
%   If a base is specified by a number, say b, the digits are [0,1,...,d-1].
% The base can also be directly an array with the digits
%   Fourth input, optional: how many digits the output should have as a
% minimum (padding with leading zeros, i.e with the first digit)
%   Non-valid digits in origin array are discarded.
%   It works with cell arrays. In this case it gives a matrix in which each
% row is padded with leading zeros if needed
%   If the base is specified as a number, digits are numbers, not
% characters as in `dec2base` and `base2dec`

if ~iscell(varargin{1}), varargin{1} = varargin(1); end
if numel(varargin{2})>1, ax = varargin{2}; bx=numel(ax); else bx = varargin{2}; ax = 0:bx-1; end
if numel(varargin{3})>1, az = varargin{3}; bz=numel(az); else bz = varargin{3}; az = 0:bz-1; end
Z = cell(size(varargin{1}));
for c = 1:numel(varargin{1})
    x = varargin{1}{c}; [valid, x] = ismember(x,ax); x = x(valid)-1;
    if ~isempty(x) && ~any(x) % Non-empty input, all zeros
        z = 0;
    elseif ~isempty(x) % Non-empty input, at least a nonzero
        z = NaN(1,ceil(numel(x)*log2(bx)/log2(bz))); done_outer = false;
        n = 0;
        while ~done_outer
            n = n + 1;
            x = [0 x(find(x,1):end)];
            y = NaN(size(x)); done_inner = false;
            m = 0;
            while ~done_inner
                m = m + 1;
                t = x(1)*bx+x(2);
                r = mod(t, bz); q = (t-r)/bz;
                y(m) = q; x = [r x(3:end)];
                done_inner = numel(x) < 2;
            end
            y = y(1:m);
            z(n) = r; x = y; done_outer = ~any(x);
        end
        z = z(n:-1:1);
    else % Empty input
        z = []; % output will be empty (unless user has required left-padding) with the
       % appropriate class
    end
    if numel(varargin)>=4 && numel(z)<varargin{4}, z = [zeros(1,varargin{4}-numel(z)) z]; end
    % left-pad if required by user
    Z{c} = z;
end
L = max(cellfun(@numel, Z));
Z = cellfun(@(x) [zeros(1, L-numel(x)) x], Z, 'uniformoutput', false); % left-pad so that
% result will be a matrix
Z = vertcat(Z{:});
Z = az(Z+1);
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
  • Sorry, I'm looking to do this for generic `q`, so this doesn't work for me. I'll make that clearer in the question. – Sam OT Nov 18 '16 at 18:23
  • Also, using your algorithm with `x = 125` and `q = 16` (I assume you meant only works for `q` up to 36; x can be anything) gives the result [7, -1], when it should be [7, 13]... – Sam OT Nov 18 '16 at 18:27
  • @SamT Sorry, there was a typo. Corrected now. For base above 36 you need to do it manually (it's essentially long division). I once wrote a function for that; let me find it – Luis Mendo Nov 18 '16 at 18:31
  • Edited with my `base2base` function – Luis Mendo Nov 18 '16 at 18:38
  • Excellent, thanks. I'll implement that on Monday :) -- looks like it should be pretty rapid too: no really long for-loop. – Sam OT Nov 18 '16 at 19:31
  • @SamT When I wrote it I didn't have code speed in mind. I don't expect it to be particularly fast, with those two nested `while` loops. You could maybe gain some speed removing some unneeded parts, like cell array support – Luis Mendo Nov 18 '16 at 19:34
  • I'm not overly concerned about speed. Because we're talking about `n^q`, I can't take `n` to be very large anyway! – Sam OT Nov 18 '16 at 20:46
  • Ah, unfortunately this doesn't work for me: I need to have 0s at the start. For example, 1 needs to be (0,0,...,0,1). I realise I didn't make this clear. With `dec2base` (which I can only use for q <= 10), I can use `dec2base(x,q,n)` to force the string to have `n` entries, adding in 0s at the start as necessary. Thanks for your help anyway – Sam OT Nov 21 '16 at 11:35
  • @SamT My function has a fourth, optional parameter that does that. I've added an example – Luis Mendo Nov 21 '16 at 12:41
1

Matlab's internal dec2base command contains essentially what you are asking for. It actually creates an array of base-10 digits before they are converted to a character array of '0'-'9' and 'A'-'Z' which is the reason for its limitation to bases <= 36.

So after removing the last step of character conversion from dec2base and modifying the error checking accordingly gives the function dec2basevect you were asking for.

The result will be a base-10 vector and you are no longer limited to bases <= 36. The most significant digit will be in index one of this vector. If you need it the other way round, i.e. least significant digit in index one, just do a fliplr to the result.

Due to copyrights by MathWorks, you have to make the necessary modifications to dec2baseon your own.

BaWue
  • 11
  • 2
  • You’ve included attribution, which is nice and correct. But unfortunately that doesn’t change the fact that you are breaking copyright law. This is proprietary, pay-for-use code. By posting it here you are re-licensing it under a CC license, which you don’t have the rights to do because you are not the copyright owner. – Cris Luengo Sep 29 '21 at 13:10
  • Ok, thank you. I will remove the code. So anyone who wants to use this has to do this modification on his own. – BaWue Sep 29 '21 at 13:17
  • I think that’s a good idea. – Cris Luengo Sep 29 '21 at 13:22