4
\$\begingroup\$

I need to write a code for conversion between the decimal numbers and binary numbers. I have tried the problem and what I have is 3 classes and 1 enum type

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * The {@code Immutable} class {@code BinaryNumber} wraps a string
 * representation of a binary (base 2) number.The class uses a
 * string of ASCII digits {@code '0'} and {@code '1'} to represent
 * a binary number.
 *
 * <p>In addition,this class provides a several methods like for
 * converting a binary(base 2) to a signed {@code int}, swapping
 * bits position in a binary number, finding the {@code 1}s and {@code two}s
 * complement of a binary number and several other useful methods for doing
 * a binary calculation.
 *
 * The minimum and maximum length of binary sequence that can be represented by
 * object of this class is {@code 0} and {@code Integer.MAX_VALUE} respectively.
 *
 * @author Praveen Kumar
 */
public class BinaryNumber implements Iterable<Character> {
    // wrapped binary string
    private final String binaryString;
    // length of the binary number represented by a binary string
    private final int length;

    /**
     * Constructs a newly allocated {@code BinaryNumber} object that represents
     * a specified string of ASCII digits in binary (base 2).
     *
     * @param binaryString string of ASCII digits in binary(base 2)
                           i.e.,{@code '0'} and {@code '1'}.
     * @throws IllegalArgumentException if the argument cannot represent a
                                        binary(base 2) number.
     */
    public BinaryNumber(String binaryString) {
        if (binaryString.matches("[01]+")) {
            this.binaryString = binaryString;
            length = binaryString.length();
        }
        else {
            throw new IllegalArgumentException(binaryString+": Invalid Entries for Binary representation");
        }
    }

    /**
     * Returns the length of this {@code BinaryNumber}.the length will be equal
     * to the total number of ASCII character in a binary string represented by
     * this {@code BinaryNumber} object.
     *
     * @return int the number of the ASCII characters({@code '0'} and {@code '1'})
                   in this object.
     */
    public int length() {
        return length;
    }

    /**
     * returns a new {@code BinaryNumber} equivalent to this {@code BinaryNumber}
     * object but having a length equals to the argument(size).
     * the new object would be padded with leading {@code 0}s or {@code 1}s accordingly the
     * unsigned decimal equivalent(with {@code 2}s complement) of this object is positive or
     * negative.
     * this method will not allow for the Narrowing conversion({@code size < length()}) and
     * throws exception in such case.
     *
     * @param size the needed size of a binary sequence represented by this object.
     * @return BinaryNumber having a length equals to the argument and is equivalent
                            to this object.
     * @throws IndexOutOfBoundsException if the specified size is less than the size
                                         of the binary sequence represented by this
                                         object.
     */
    public BinaryNumber toSize(int size) {
        if  (size < length()) {
            throw new IndexOutOfBoundsException(size+": Narrowing Conversion is not allowed.");
        }
        StringBuilder mutableBinString = new StringBuilder(binaryString);
        mutableBinString.reverse();
        while (mutableBinString.length() != size) {
            if(isNegative()) {
                mutableBinString.append('1');
            }
            else {
                mutableBinString.append('0');
            }
        }
        mutableBinString.reverse();
        BinaryNumber binaryNum = new BinaryNumber(mutableBinString.toString());
        if (isNegative()) {
        return binaryNum.swapBitsPosn(0, size - length);
        }
        return binaryNum;
    }

    /**
     * returns a {@code String} object representing a binary sequence in this object.
     *
     *@return String representation of binary sequence(base 2) represented by this object.
     */
    @Override
    public String toString() {
        return binaryString;
    }

    /**
     * returns a new {@code BinaryNumber} having a binary sequence equivalent to performing a
     * {@code 1}s complement on the binary sequence represented by this object.
     *
     * @return BinaryNumber the {@code 1}s complement of the char sequence represented by
                            this object.
     */
    public BinaryNumber onesComplement() {
        StringBuilder mutableBinString = new StringBuilder(binaryString);
        for (int i = 0; i < mutableBinString.length(); i++) {
            char flippedBit = (mutableBinString.charAt(i) == '0' ? '1' : '0');
            mutableBinString.setCharAt(i, flippedBit);
        }
        return new BinaryNumber(mutableBinString.toString());
    }

    private class BinaryIterator implements Iterator<Character> {
        char[] bits = binaryString.toCharArray();

        int index = 0;

        /*
         * Tests if the iteration has more bit character left while traversing
         * in the char sequence represented by this object.
         *
         * @return {@code true} if the binary sequence represented by this object
                               has one or more bit character left for iteration;
                               {@code false} otherwise.
         */
        @Override
        public boolean hasNext() {
            return index < bits.length;
        }

        /*
         * returns the next bit {@code Character} in the binary sequence represented
         * by this object while iteration.
         *
         * @return Character the next bit character in the binary sequence represented
                             by this object.
         * @throws NoSuchElementException if the binary sequence reprsented by this
                                          object doesnot have next bit {@code Character}.
         */
        @Override
        public Character next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            else {
                return bits[index++];
            }
        }

        /* this method will throws an exception every time when it is invoked because this
         * operation is not supported on the binary sequence represented by this object.
         *
         * @throws UnsupportedOperationException every time when it is invoked.
         */
        @Override
        public void remove() {
            throw new UnsupportedOperationException("Removal: Invalid Operation");
        }
    }

    /**
     * returns a iterator of {@code Character} over a binary sequence represented by this object.
     *
     * @return {@code Iterator<Character>} the iterator providing the iteration on bit character
                                           of the binary sequence represented by this object.
     */
    @Override
    public Iterator<Character> iterator() {
        return new BinaryIterator();
    }

    /**
     * Tests if the signed decimal(with {@code 2}s complement) equivalent of the binary sequence
     * represented by this object is negative.
     *
     * @return {@code true} if the signed decimal equivalent of the binary sequence represented by
                            this object is negative;{@code false} otherwise.
     */
    public boolean isNegative() {
        char signBit = binaryString.charAt(0);
        return signBit == '1' ? true : false;
    }

    /**
     * returns a new {@code BinaryNumber} having a binary sequence equivalent to the binary sequence
     * of this object but with the bits position of the {@code i}th and the {@code j}th index of the
     * binary sequence(in this object) interchanged(in the new object).
     *
     * @return BinaryNumber with binary sequence having the {@code i}th and the {@code j}th index
                            interchanged in the binary sequence represented by this object.
     * @throws IndexOutOfBoundsException if either argument {@code i} or {@code j} is negative or
     *                                   greater than the length of the binary sequence represented
                                         by this object.
     */
    public BinaryNumber swapBitsPosn(int i, int j) {
        boolean swapPossibility = (i >= 0) && (j >=0) && (i < length) && (j < length);
        if (!swapPossibility) {
            throw new IndexOutOfBoundsException("Illegal Index");
        }
        StringBuilder mutableBinString = new StringBuilder(this.binaryString);
        char ithBit = mutableBinString.charAt(i);
        mutableBinString.setCharAt(i, mutableBinString.charAt(j));
        mutableBinString.setCharAt(j, ithBit);
        return new BinaryNumber(mutableBinString.toString());
    }

    /**
     * returns a {@code int} equivalent of a binary sequence represented by
     * this object.this invocation is only allowed on the {@code BinaryNumber}
     * object having a length less than or equal to {@code Integer.SIZE}
     * ;otherwise the invocation will throw an exception.
     *
     * @return signed integer(with {@code 2}s complement) equivalent of the
     * binary sequence represented by this object.
     * @throws UnsupportedOperationException if the length of the binary sequence
                                             represented by this object is greater
                                             than the {@code Integer.SIZE}.
     */
    public int toDecimalInt() {
        if (length() > Integer.SIZE) {
            throw new UnsupportedOperationException("Integer can only hold "+Integer.SIZE+" bits.");
        }
        int decInt = 0;
        BinaryNumber binaryNum = this;
        boolean Negative = isNegative();
        if (Negative) {
            binaryNum = twosComplement();
        }
        Iterator<Character> iterator = binaryNum.iterator();
        for (int i = 0; iterator.hasNext(); i++) {
            char next = iterator.next();
            if (next == '1') {
                decInt = decInt + twoRaisedToPow((binaryNum.length() - i - 1));
            }
        }
        return Negative ? -decInt : decInt;
    }

    private static int twoRaisedToPow(int i) {
        assert (i > 0 && i < 32) : "invalid shift";
        return 1 << i;
    }

    /**
     * returns the {@code 2}s complement representation of the binary
     * sequence represented by this object.
     *
     * @return BinaryNumber having the binary sequence equivalent to the
                            {@code 2}s complement of the binary sequence
                            represented by this object.
     */
    public BinaryNumber twosComplement() {
        BinaryNumber binNum = onesComplement();
        StringBuilder mutableBinString = new StringBuilder(binNum.binaryString);
        mutableBinString.reverse();
        for (int i = 0; i < binNum.length(); i++) {
            if (mutableBinString.charAt(i) == '1') {
                mutableBinString.setCharAt(i, '0');
                continue;
            }
            mutableBinString.setCharAt(i, '1');
            break;
        }
        mutableBinString.reverse();
        return new BinaryNumber(mutableBinString.toString());
    }
}

DecimalNumber.java

/**
 * The {@code DecimalNumber} class wraps a primitive data type {@code int}.
 * The object of type {@code DecimalNumber} will be helpful when doing a
 * conversion of a type {@code int} to {@code BinaryNumber}.
 *
 * @author Praveen Kumar
 */
public class DecimalNumber {
    // wrapped int
    private final int value;

    /**
     * Constructs a newly allocated {@code DecimalNumber} object that represents
     * the specified {@code int} value.
     *
     * @param integer The primitive {@code int} value represented by {@code this}
     * object.
     */
    public DecimalNumber(int value) {
        this.value = value;
    }

    /**
     * Returns a {@code BinaryNumber}(base 2)
     * representation of unsigned {@code int} equivalent to {@code int}
     * represented by {@code this} object.
     * The unsigned {@code int} equivalent is the {@code int} represented by
     * this object plus 2<sup>32</sup> if the represented {@code int} is negative;
     * otherwise it is equal to the represented {@code int} of {@code this} object.
     * for clearity here is the example: let the represented {@code int = -8} than the
     * equivalent unsigned {@code int} will be 2<sup>32</sup> {@code + (-8)}.
     *
     * <p>The {@code BinaryNumber} returned by this method will contains the leading
     * {@code 0}s if the size of the binary string equivalent to the {@code int}
     * represented by {@code this} object is less than 32.
     * and in that case The number of leading {@code 0}s will be 32 minus the minimum number
     * of bits required to convert {@code int} to a binary number.
     *
     * @return BinaryNumber the binary equivalent of the {@code int} represented by
               {@code this} object.
     */
    public BinaryNumber toBinaryNumber() {
        int i = Math.abs(value);
        StringBuilder mutableBinString = new StringBuilder();
        while (i != 0) {
            mutableBinString.append(isEven(i) ? '0' : '1');
            i >>>= 1;
        }
        mutableBinString.append('0');
        mutableBinString.reverse();
        BinaryNumber binaryNum;
        if (value < 0) {
            binaryNum = new BinaryNumber(mutableBinString.toString()).twosComplement();
            return binaryNum.toSize(Integer.SIZE);
        }
        binaryNum = new BinaryNumber(mutableBinString.toString());
        return binaryNum.toSize(Integer.SIZE);
    }

    /**
     * Tests if the specified argument is a Even Number.
     *
     *@param i the {@code int} to be tested.
     *@returns {@code true} if the arugument is a Even Number;{@code false} otherwise.
     */
    public static boolean isEven(int i) {
        return (i & 1) == 0;
    }
}

enum ConversionType.java for representing conversions binary to decimal and decimal to binary.

/**
 * Enum for conversion from one base to another like from decimal to binary,
 * binary to decimal etc.Every constant represent a specific conversion type
 * like for Example: the {@link BinaryNumber#DECIMAL_TO_BINARY DECIMAL_TO_BINARY}
 * represent a conversion from base 10 to base 2.
 *
 * @author Praveen Kumar
 */
public enum ConversionType {
    /**
     * Represents a conversion type from base 10 to base 2.
     */
    DECIMAL_TO_BINARY {
        @Override
        public String convert(String arg) throws IllegalArgumentException {
            try {
                int integer = Integer.parseInt(arg);
                DecimalNumber decimalNum = new DecimalNumber(integer);
                return decimalNum.toBinaryNumber().toString();
            }
            catch(NumberFormatException cause) {
                throw new IllegalArgumentException(cause);
            }
        }
    },
    /**
     * Represents a conversion type from base 2 to base 10.
     */
    BINARY_TO_DECIMAL {
        @Override
        public String convert(String arg) throws IllegalArgumentException {
            try {
                BinaryNumber binaryNum = new BinaryNumber(arg);
                int value = binaryNum.toDecimalInt();
                return "" + value;
            }
            catch(IllegalArgumentException cause) {
                throw new IllegalArgumentException(cause);
            }
            catch(UnsupportedOperationException cause) {
                throw new IllegalArgumentException(cause);
            }

        }
    };

    /**
     * Converts the argument to a string representing the another base and is equivalent
     * to the base represented by the argument.the conversion will take place accordingly
     * with the Enum constant {@code ConversionType}.
     *
     * @param arg the string representation of the base to be converted to another base.
     * @return the string representation of a converted base.
     * @throws IllegalArgumentException if the argument string is not parsable to the
                                        base to be converted.
     */
    public abstract String convert(String arg) throws IllegalArgumentException;
}

finally the functional class whose object is providing the functions of doing a conversion.

public class Convertor {
    private final ConversionType conversionType;

    public Convertor(ConversionType conversionType) {
     this.conversionType = conversionType;
    }

    public String convert(String arg) {
     return conversionType.convert(arg);
    }

    public static void main(String... args) {
     Convertor con = new Convertor(ConversionType.DECIMAL_TO_BINARY);
     // conversion code goes here...
    }
}

With decimal number I have initially made a convention to deal with Java int for a start. Now I just want to know if I can make this code better. I want to stick to OO design as much as I can.

\$\endgroup\$
  • \$\begingroup\$ Question: Why isn't binaryNumber a wrapped int just like decimalNumber? Seems like everything your functions do to the binary number could be done more easily to an int than to a string. \$\endgroup\$ – JS1 Feb 17 '17 at 18:17
1
\$\begingroup\$

Stuff I noticed, from start to end:


private final String binaryString;

This limits you in what you can do with your wrapped number effectively. I strongly suggest considering to instead use a char[].


throwing an IllegalArgumentException in a constructor is basically bad form, because instead of resulting in an IllegalArgumentException, the JLS mandates that the Exception be wrapped into an ExceptionInInitializerError which makes all this stuff moot. (For details see JLS 12 §4.2)
Long story short: I'm pretty sure you want a factory method that performs the argument validation for you like:

public static BinaryNumber fromString(String binaryString) {
    if (!binaryString.matches("[01]+")) {
        throw new IllegalArgumentException(...);
    }
    return new BinaryNumber(binaryString);
}

Note that this requires you to make the constructor private to avoid messes with incorrect initialization.


Why is length cached in the code? It's always correct to use binaryString.length() instead, isn't it?


toSize could be significantly easier as:

if (size < binaryString.length()) {
    throw new ...
}
final char padChar = isNegative() ? '1' : '0';
final int padCount = binaryString.length() - size;
final StringBuilder mutableContainer = new StringBuilder(binaryString);
for (int i = 0; i < padCount; i++) {
    mutableContainer.insert(0, padChar);
}
BinaryNumber binaryNum = new BinaryNum(mutableContainer.toString());
...

note that this could be quite a bit cleaner if you used a char[]. If you did that, you could have done something like this:

char[] newRepresentation = new char[size];
Arrays.fill(newRepresentation, isNegative() ? '1' : '0');
System.arraycopy(representation, 0, newRepresentation, size - representation.length, representation.length);
// do that strange swapBitsPosn thing here

This is conceptually much simpler (and quite probably faster) than putting it into a StringBuffer, reversing twice and in the middle appending chars in a loop (which are determined by an if-statement depending on a method call ...)


The one's complement could be easier if you worked on a char[], too... how is an exercise left to the reader.


return signBit == '1' ? true : false;

should be return signBit == '1';, which is the same, but shorter and quicker...


Did I also mention that swapBitsPosn would also be simpler with a char[]??? Well I'm feeling like a broken record now ...


toDecimalInt can be significantly simplified when you start using binary arithmetics and binary operators ...

int decInt = 0;
// pad the representation to integer size to simplify shifting
char[] representation = toSize(Integer.SIZE).representation;
for (int i = 0; i < representation.length; i++) {
    if (representation[i] == '1') {
        decInt |= 1;
    }
    decInt << 1;
}
return decInt;

Note that this completely eliminates the need for a twoRaisedToPow...


You can get rid of reverse in twosComplement when you make the for-loop iterate from the back like so:

for (int i = binNum.length() - 1; i >= 0; i--) {
// repeat code here
}

I'll just ignore the rest of the code for now, but I think you get the gist ...

\$\endgroup\$
  • \$\begingroup\$ Throwing exceptions in a constructor will not lead to an ExceptionInInitializerError, only exceptions thrown in static initializers/initializers of static variables are wrapped. \$\endgroup\$ – Nevay Jun 17 '17 at 21:19
  • \$\begingroup\$ @Nevay aahh you're right ... I must've mixed up 12.4 and 12.5 from the JLS ("Initialization" and "Creation of new Class Instances") a bit... \$\endgroup\$ – Vogel612 Jun 18 '17 at 11:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.