15

Terminology

In this question I am calling "floating point number" "decimal number" to prevent ambiguation with the float/double Java primitive data types. The term "decimal" has no relationship with "base 10".

Background

I am expressing a decimal number of any base in this way:

class Decimal{
    int[] digits;
    int exponent;
    int base;
    int signum;
}

which approximately expresses this double value:

public double toDouble(){
    if(signum == 0) return 0d;
    double out = 0d;
    for(int i = digits.length - 1, j = 0; i >= 0; i--, j++){
        out += digits[i] * Math.pow(base, j + exponent);
    }
    return out * signum;
}

I am aware that some conversions are not possible. For example, it is not possible to convert 0.1 (base 3) to base 10, because it is a recurring decimal. Similarly, converting 0.1 (base 9) to base 3 is not possible, but covnerting 0.3 (base 3) is possible. There are probably other cases that I have not considered.

The traditional way

The traditional way (by hand) of change of base, for integers, from base 10 to base 2, is to divide the number by the exponents of 2, and from base 2 to base 10 is to multiply the digits by respective exponents of 2. Changing from base x to base y usually involves converting to base 10 as an intermediate.

First question: Argument validation

Therefore, my first question is, if I were to implement the method public Decimal Decimal.changeBase(int newBase), how can I validate whether newBase can be made without resulting in recurring decimals (which is incompatible with the design of the int[] digits field, since I don't plan to make an int recurringOffset field just for this.

Second question: Implementation

Hence, how to implement this? I instinctively feel that this question is much easier to solve if the first question is solved.

Third question: What about recurring number output:

I don't plan to make an int recurringOffset field just for this.

For the sake of future readers, this question should also be asked.

For example, according to Wolfram|Alpha:

0.1 (base 4) = 0.[2...] (base 9)

How can this be calculated (by hand, if by programming sounds too complicated)?

I think that a data structure like this can represent this decimal number:

class Decimal{
    int[] constDigits;
    int exponent;
    int base;
    int signum;
    @Nullable @NonEmpty int[] appendRecurring;
}

For example, 61/55 can be expressed like this:

{
    constDigits: [1, 1], // 11
    exponent: -1, // 11e-1
    base: 10,
    signum: 1, // positive
    appendRecurring: [0, 9]
}


Not a homework question

I am not looking for any libraries. Please do not answer this question with reference to any libraries. (Because I'm writing this class just for fun, OK?)

Spektre
  • 41,942
  • 8
  • 91
  • 312
SOFe
  • 7,134
  • 4
  • 28
  • 57

3 Answers3

6

To your first question: whenever the prime factors of the old base are also among the prime factors of the new base you can always convert without becoming periodic. For example every base 2 number can be represented exactly as base 10. This condition is unfortunately sufficient but not necessary, for example there are some base 10 numbers like 0.5 that can be represented exactly as base 2, although 2 does not have the prime factor 5.

When you write the number as fraction and reduce it to lowest terms it can be represented exactly without a periodic part in base x if and only if the denominator has only prime factors that also appear in x (ignoring exponents of primes).

For example, if your number is 3/25 you can represent this exactly in every base that has a prime factor 5. That is 5, 10, 15, 20, 25, ...

If the number is 4/175, the denominator has prime factors 5 and 7 and therefore can be represented exactly in base 35, 70, 105, 140, 175, ...

For implementation, you can either work in the old base (basically doing divisions) or in the new base (basically doing multiplications). I would avoid going through a third base during the conversion.

Since you added periodic representations to your question the best way for conversion seems to be to convert the original representation to a fraction (this can always be done, also for periodic representations) and then convert this to the new representation by carrying out the division.

Henry
  • 40,427
  • 6
  • 56
  • 72
2

To answer the third part of the question, once you have your fraction reduced (and you found out that the "decimal" expansion will be a recurring fraction), you can detect the recurring part by simply doing the long-hand division and remembering the remainders you've encountered.

For example to print out 2/11 in base 6, you do this:

2/11    = 0 (rem 2/11)
2*6/11  = 1 (rem 1/11)
1*6/11  = 0 (rem 6/11)
6*6/11  = 3 (rem 3/11)
3*6/11  = 1 (rem 7/11)
7*6/11  = 3 (rem 9/11)
9*6/11  = 4 (rem 10/11)
10*6/11 = 5 (rem 5/11)
5*6/11  = 2 (rem 8/11)
8*6/11  = 4 (rem 4/11)
4*6/11  = 2 (rem 2/11) <-- We've found a duplicate remainder

(Had 2/11 been convertible to a base 6 number of finite length, we would've reached 0 remainder instead.)

So your result will be 0.[1031345242...]. You can fairly easily design a data structure to hold this, bearing in mind that there could be several digits before the recurrence begins. Your proposed data structure is good for this.

Personally I'd probably just work with fractions, floating point is all about trading in some precision and accuracy for compactness. If you don't want to compromise on precision, floating point is going to cause you a lot of trouble. (Though with careful design you can get pretty far with it.)

biziclop
  • 46,403
  • 12
  • 73
  • 97
  • Sometimes duplicated numbers don't necessarily mean that it is another recursion. – SOFe Aug 01 '16 at 17:15
  • 2
    @PEMapModder Don't look at the result for duplicates, look at the remainder. When that is duplicated, that is definitely the cycle starting again. – biziclop Aug 01 '16 at 18:43
2

I waited with this after the reward because this is not directly an answer to your questions rather few hints how to approach your task instead.

  1. Number format

    Arbitrary exponential form of number during base conversion is a big problem. Instead I would convert/normalize your number to form:

    (sign) mantissa.repetition * base^exp
    

    Where unsigned int exp is the exponent of least significant digit of mantissa. The mantissa,repetition could be strings for easy manipulation and printing. But that would limit your max base of coarse. For example if you reserve e for exponent then you can use { 0,1,2,..9, A,B,C,...,Z } for digits so max base would be then only 36 (if not counting special characters). If that is not enough stay with your int digit representation.

  2. Base conversion (mantissa)

    I would handle mantissa as integer number for now. So the conversion is done simply by dividing mantissa / new_base in the old_base arithmetics. This can be done on strings directly. With this there is no problem as we can always convert any integer number from any base to any other base without any inconsistencies,rounding or remainders. The conversion could look like:

    // convert a=1024 [dec] -> c [bin]
    AnsiString a="1024",b="2",c="",r="";
    while (a!="0") { a=divide(r,a,b,10); c=r+c; }
    // output c = "10000000000"
    

    Where:

    • a is number in old base which you want to convert
    • b is new base in old base representation
    • c is number in new base

    Used divide function looks like this:

    //---------------------------------------------------------------------------
    #define dig2chr(x)  ((x<10)?char(x+'0'):char(x+'A'-10))
    #define chr2dig(x)  ((x>'9')?BYTE(x-'A'+10):BYTE(x-'0'))
    //---------------------------------------------------------------------------
    int        compare(             const AnsiString &a,const AnsiString &b);           // compare a,b return { -1,0,+1 } -> { < , == , > }
    AnsiString divide(AnsiString &r,const AnsiString &a,      AnsiString &b,int base);  // return a/b computed in base and r = a%b
    //---------------------------------------------------------------------------
    int compare(const AnsiString &a,const AnsiString &b)
        {
        if (a.Length()>b.Length()) return +1;
        if (a.Length()<b.Length()) return -1;
        for (int i=1;i<=a.Length();i++)
            {
            if (a[i]>b[i]) return +1;
            if (a[i]<b[i]) return -1;
            }
        return 0;
        }
    //---------------------------------------------------------------------------
    AnsiString divide(AnsiString &r,const AnsiString &a,AnsiString &b,int base)
        {
        int i,j,na,nb,e,sh,aa,bb,cy;
        AnsiString d=""; r="";
        // trivial cases
        e=compare(a,b);
        if (e< 0) { r=a;  return "0"; }
        if (e==0) { r="0"; return "1"; }
        // shift b
        for (sh=0;compare(a,b)>=0;sh++) b=b+"0";
        if (compare(a,b)<0) { sh--; b=b.SetLength(b.Length()-1); }
    
        // divide
        for (r=a;sh>=0;sh--)
            {
            for (j=0;compare(r,b)>=0;j++)
                {
                // r-=b
                na=r.Length();
                nb=b.Length();
                for (i=0,cy=0;i<nb;i++)
                    {
                    aa=chr2dig(r[na-i]);
                    bb=chr2dig(b[nb-i]);
                    aa-=bb+cy; cy=0;
                    while (aa<0) { aa+=base; cy++; }
                    r[na-i]=dig2chr(aa);
                    }
                if (cy)
                    {
                    aa=chr2dig(r[na-i]);
                    aa-=cy;
                    r[na-i]=dig2chr(aa);
                    }
                // leading zeros removal
                while ((r.Length()>b.Length())&&(r[1]=='0')) r=r.SubString(2,r.Length()-1);
                }
            d+=dig2chr(j);
            if (sh) b=b.SubString(1,b.Length()-1);
            while ((r.Length()>b.Length())&&(r[1]=='0')) r=r.SubString(2,r.Length()-1);
            }
    
        return d;
        }
    //---------------------------------------------------------------------------
    

    It is written in C++ and VCL. AnsiString is VCL string type with self allocating properties and its members are indexed from 1.

  3. Base conversion (repetition)

    There are 2 approaches for this I know of. The simpler but with possible round errors is setting the repetition to long enough string sequence and handle as fractional number. For example rep="123" [dec] then conversion to different base would be done by multiplying by new base in old base arithmetics. So let create long enough sequence:

    0 + 0.123123123123123 * 2
    0 + 0.246246246246246 * 2
    0 + 0.492492492492492 * 2
    0 + 0.984984984984984 * 2
    1 + 0.969969969969968 * 2
    1 + 0.939939939939936 * 2
    1 + 0.879879879879872 * 2 ...
    ------------------------------
    = "0.0000111..." [bin]
    

    With this step you need to make repetition analysis and normalize the number again after exponent correction step (in next bullet).

    Second approach need to have the repetitions stored as division so you need it in form a/b in old_base. You just convert a,b as integers (the same as mantissa) and then do the division to obtain fractional part + repetition part.

    So now you should have converted number in form:

    mantissa.fractional [new_base] * old_base^exp
    

    or:

    mantissa.fractional+a/b [new_base] * old_base^exp
    
  4. Base conversion (exponent)

    You need to change old_base^old_exp to new_base^new_exp. The simplest way is to multiply the number by the old_base^old_exp value in new base arithmetics. So for starters multiply the whole

    mantissa.fractional+(a/b) [new_base]
    

    by old_base old_exp times in the new arithmetics (later you can change it to power by squaring or better). And after that normalize your number. So find where the repetition string begins and its digit position relative to . is the new_exp value.

[Notes]

For this you will need routines to convert old_base and new_base between each other but as the base is not bignum but just simple small unsigned int instead it should not be any problem for you (I hope).

Spektre
  • 41,942
  • 8
  • 91
  • 312