6

I want to write a program which can converts one unit to another unit. Let's say I have 2 methods.First method can do metric conversions, second method can do weight conversitons. For example;

1. long km=metricConvLength(long mil,Enum.mil,Enum.km);//first method

2. long agirlik=metricConvWeight(long kg,Enum.mil,Enum.km);//second method

I want to use Enum struct for these variables. My program can convert these things and opposites;

  • sea mile-km
  • sea mile-mile
  • feet- km
  • feet- mil
  • pound- kg
  • ons- gr
  • inc - cm
  • yard - m
  • knot- km

My Question: I don't want to use if-else or switch-case structs for conversions.(Because if I use if-else struct,my code looks like so bad, much easy and slow.And I need more then 50 if-else struct when if I use these struct.This is grind.)

Can I write an algorithm for these conversions without using if-else or switch-case. My purpose is less code, more work. Any tips about algorithm?

zEro
  • 1,255
  • 14
  • 24
TeachMeJava
  • 640
  • 1
  • 13
  • 35

5 Answers5

14

You do not need an if-then-else - in fact, you do not need control statements in your program. All you need is a lookup table - a Map that translates your unit enum to a double conversion factor, such that multiplying the measure in units by the conversion factor you get meters for units of space, and kilos for units of weight. Conversely, dividing meters by that factor gives you the desired units.

With this map in hand, you can do conversions for all pairs of units:

  • Look up the conversion factor Cs for the source units
  • Look up the conversion factor Cd for the destination units
  • Return value * Cs / Cd as your result.

For example, let's say that you want to deal with meters, yards, inches, and feet. Your map would look like this:

  • m - 1.0
  • y - 0.9144
  • in - 0.0254
  • ft - 0.3048

Now let's say you want to convert 7.5 yards to feet:

  • Look up Cs = 0.9144
  • Look up Cd = 0.3048
  • Compute and return Res = 7.5 * 0.9144 / 0.3048 = 22.5
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Why not just make it a 2-d table, where dimension 1 is the input units, dimension 2 is the output units, and the resulting entry is the conversion rate? – pjs Jul 09 '13 at 13:54
  • @pjs Because instead of `N` items you'd need to store `N^2` items. This is OK when the number of units that you want to support is small, but it grows really fast: for 20 unit pairs you'd need to compute 400 factors, giving you 380 more spots to make a mistake while typing in a conversion factor. – Sergey Kalinichenko Jul 09 '13 at 14:00
2

I would suggest to introduce a type "MetricValue". The history of software engineering is full of projects where programmers forgot the units of the values they used. The resulting desasters are wellknown.

Moreover, I would propose an immutable class as it makes many design aspects more simple.

Defining a base unit helps you a lot as you first convert all values to it and then convert the value to your target unit. You could also apply a matrix but then you would need to precalculate the values. The size of your matrix will have the size of n^2 while the conversion from and to the base unit remains only n values. If you use complicated conversion formulas which are resource-consuming, then it makes sense. Otherwise, the benefit will be (too) small regarding the efforts.

What about this code?

public class MetricValue {

    public static enum Unit {
        m(1.0d), y(0.9144d), in(0.0254d), ft(0.3048d);

        final static Unit baseUnit = m;
        final double perBaseUnit;

        private Unit(double inM) {
            this.perBaseUnit = inM;
        }

        public double fromBaseUnit(double value) {
            return value / perBaseUnit;
        }

        public double toBaseUnit(double value) {
            return value * perBaseUnit;
        }
    }

    final double value;
    final Unit unit;

    public MetricValue(double value, Unit unit) {
        super();
        this.value = value;
        this.unit = unit;
    }

    public MetricValue to(Unit newUnit) {
        Unit oldUnit = this.unit;
        return new MetricValue(newUnit.fromBaseUnit(oldUnit.toBaseUnit(value)),
                newUnit);
    }

    @Override
    public String toString() {
        return value + " " + unit.name();
    }

    public static void main(String[] args) {
        MetricValue distanceInM = new MetricValue(1, Unit.m);
        MetricValue distanceInFt = new MetricValue(6, Unit.ft);
        System.out.println(distanceInM);

        System.out.println(distanceInM.to(Unit.y));
        System.out.println(distanceInM.to(Unit.y).to(Unit.m));
        System.out.println(distanceInM.to(Unit.y).to(Unit.ft));
        System.out.println(distanceInFt.to(Unit.m));
    }
}

You can use the raw value and create a new metric value when you calculate. You could also add operations for basic or sophisticated arithmetic operations.

The getters were omitted here.

mmirwaldt
  • 843
  • 7
  • 17
1

Associate their relative value to variables in your Enums (I guess you have one for distances, one for weights) like enum Distances { CM(100), M(100*1000), IN(254) },... and then you just have to get the ratio of the values provided, multiply by the first param, and you have your result. Use a base 100 or 1000 for the smallest unit if you want a decent precision.

C4stor
  • 8,355
  • 6
  • 29
  • 47
  • Or just use a [rational number](http://stackoverflow.com/questions/5442640/is-there-a-commonly-used-rational-numbers-library-in-java). – kennytm Jul 09 '13 at 12:35
  • 1
    Yes, but (if I'm not wrong) that adds an extra function (probably full of if) whereas associating enum values to int is offered by the language – C4stor Jul 09 '13 at 12:37
  • What about Matrix? Can we use matrix for conversition? – TeachMeJava Jul 09 '13 at 12:41
1

Choose one of the units to be the "base" unit (one for weight, e.g., gr; and one for distance, e.g., m). Then add methods toBaseUnit and fromBaseUnit to your enum using conversion ratios for each of your values.

No "ifs" involved, and your conversion methods will look like this:

ResultingUnit.fromBaseUnit(originalUnit.toBaseUnit(value));

Sample enum:

public enum Distance {
    METER(new BigDecimal("1.0")), // Base Unit is METER
    KM(new BigDecimal("1E3")),
    CM(new BigDecimal("1E-2"));

    private final static MathContext MC = 
          new MathContext(30, RoundingMode.HALF_EVEN);
    private final BigDecimal conversionRatio;

    Distance(BigDecimal conversionRatio) {
        this.conversionRatio = conversionRatio;
    }

    long fromBaseUnit(BigDecimal baseUnit) {
        return baseUnit.divide(conversionRatio, MC).longValue();
    }

    // returns BigDecimal to avoid rounding two times
    // and possible division by zero
    BigDecimal toBaseUnit(long originalUnit) {
        return BigDecimal.valueOf(originalUnit).multiply(conversionRatio);
    }
}

And the adapted conversion method:

public long metricConvLength(long value, Distance orgUnit, Distance resultUnit) {
    return resultUnit.fromBaseUnit(orgUnit.toBaseUnit(value));
}  

Besides being slightly faster (by avoiding Map lookups and looping through the enum values), the major advantage of this approach is that if you ever need more complex conversion operations (say, a Temperature enum with Celsius, Farenheit and Kelvin) you can override fromBaseUnit and toBaseUnit in the enum value body.


Working Example.

I've used BigDecimal for the conversion ratio and internal calculations in order to have greater control over precision and rounding behavior.

Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118
1

In your Enum you could implement a fromString method on an enum type, Lets say your Enum name is 'Distances'

private static final Map<String,Distances> stringToEnum = new HashMap<String,Distances>;

Initialize the map:

  static{
        for(Distances distance: values()) {
            stringToEnum.put(distance.toString(),distance);
        }
  }

Return enumType for String:

public static Distances getDistance(String stringValue){
    return stringToEnum.get(stringValue);
}

In your methods you could just pass in:

Distance.getDistance("feet").getCalculatedDistance()
zEro
  • 1,255
  • 14
  • 24
Garreth Golding
  • 985
  • 2
  • 11
  • 19