tags:

views:

114

answers:

6

I receive a decimal number with a maximum of 4 digits after the "." and I know this number is in milligram.

I have to find the best matching unit (milligram, gram, kilogram) for the number.

for an example if I receive

edited

116000000.0000 milligram, it's going to return 116.0000 kilogram
66990000.0000 milligram, it's going to return 66.9900 kilogram
49000010.0000 milligram, it's going to return 49000.0100 g
49000000.0100 milligram, it's going to return 49000000.0100 milligram
1001 milligram, it's going to return 1.0010 gram
1010 milligram, it's going to return 1.0100 gram
1000 milligram, it's going to return 0.0010 kilogram
1100 milligram, it's going to return 0.0011 kilogram
135005 milligram, it's going to return 135.0050 gram
and last sample 10013500 milligram, it's going to return 10.0135 kilogram

I'm currently using this code, which I think look/is ugly and can fail

Dim temp As Decimal
Dim u = New List(Of Integer)(New Integer() {1, 1000, 1000000})
For i = 0 To u.Count - 1
    temp = CDec(qty / u(i))
    If (temp * 10000) - Math.Truncate(temp * 10000) <> 0 AndAlso (temp * 10000) - Math.Truncate(temp * 10000) < 1 Then
        temp = CDec(qty / u(i - 1))
        Exit For
    End If
Next
qty = temp

is there a better/nicer way of doing what I do?

edit for precision

the input can be any decimal between 0.0001 and maximum that a decimal can accept in .net

the output need to be rounded to the best unit with a maximum of 4 digits after "." without losing any precision

A: 

Your sample doesn't make too much sense, but your should go with something like this:

static string FormatWeight(double value)
{
    if (value > 10000000) return (value / 10000000D).ToString("0.#### t");
    if (value > 100000) return (value / 100000D).ToString("0.#### kg");
    if (value > 1000) return (value / 1000D).ToString("0.#### g");
    return value.ToString("0.#### mg");
}
Rubens Farias
I will lose precision with this, if I pass in 1111111.1111, I will only see 1.11, where is all the rest?
Fredou
still losing precision, i will see 1.1111, losing 0.00001111111, in the case of having 1111111.1111, I have to show 1111111.1111 milligram
Fredou
I updated my question
Fredou
Sorry, I updated my question again with more detail and more example, I forgot to tell that I had to return a decimal with only 4 digits after the ".".
Fredou
why 1001 = g and 1000 = kg? 49000010.0000 = g and 49000000.0100 = mg? Doesn't make any sense...
Rubens Farias
1001 milligram is 1.0010 gram, 1000 milligram is 0.0010kg, 49000010.0000 milligram is 49000.0100 gram and 49000000.0100 milligram is already rounded to the best unit which is 49000000.0100 milligram
Fredou
So your sample is completely messed; I updated my answer to something which actually makes sense
Rubens Farias
I just reviewed and fixed my samples, I fixed some issues.
Fredou
A: 

Why don't you just use ifs? something like:

if (num > 1000000) 
return string.format("{0.####} kg", num/1000000)

if (num > 1000) 
return string.format("{0.####} g", num/1000);

return string.format("{0.####} mg", num);
statichippo
I will lose precision with this, if I pass in 1111111.1111, I will only see 1.11, where is all the rest?
Fredou
add a D at the end of the divisor to make it a double. Eg. 1000D
statichippo
A: 

I wouldn't be above doing a CStr on the number, counting the characters, removing all trailing zeros, counting the number again, and deciding on a unit based on the number of characters in your string. Then just divide the original number by the correct magnitude and append the unit.

Before doing that, though, you'd want to do a Truncate on the original and see if the returned value matches the original; then you just use mg.

Probably won't work any better than your code, but it might be easier to read. Maybe.

iandisme
A: 

would you not be better either implementing or using a unit library for the conversions, then formatting the results afterwards?

some projects here and here, although I can't vouch for the quality of any of it...

and java one discussed here

Sam Holder
+3  A: 

Gen the numbers and choose the suitable one.

    public static decimal FormatDecimal(decimal i)
    {
        decimal milli = i;
        decimal grams = decimal.Round(i / 1000m, 4);
        decimal kilo = decimal.Round(grams / 1000m, 4);

        if (kilo * 1000 * 1000 == milli)
        {
            return kilo;
        }
        if (grams * 1000 == milli)
        {
            return grams;
        }
        return milli;
    }

And to test:

    public static void FormatDecimalTest()
    {
        if (FormatDecimal(116000000.0000m) == 116.0000m)
            Console.WriteLine("ok1");
        if (FormatDecimal(66990000.0000m) == 66.9900m)
            Console.WriteLine("ok2");
        if (FormatDecimal(49000010.0000m) == 49000.0100m)
            Console.WriteLine("ok3");
        if (FormatDecimal(49000000.0100m) == 49000000.0100m)
            Console.WriteLine("ok4");
        if (FormatDecimal(1001m) == 1.0010m)
            Console.WriteLine("ok5");
        if (FormatDecimal(1000m) == 0.0010m)
            Console.WriteLine("ok6");
        if (FormatDecimal(1100m) == 0.0011m)
            Console.WriteLine("ok7");
        if (FormatDecimal(1100m) == 0.0011m)
            Console.WriteLine("ok8");
        if (FormatDecimal(135005m) == 135.0050m)
            Console.WriteLine("ok9");
        if (FormatDecimal(10013500m) == 10.0135m)
            Console.WriteLine("ok10");
    }

In your question, I see you used a loop over the various factors. Here's a looping solution that will find the first factor that does not lose precision.

    public static decimal FormatDecimal(decimal i)
    {
        List<decimal> myFactors = new List<decimal>()
        { 1000m * 1000m, 1000m};

        foreach (decimal conversionFactor in myFactors)
        {
            decimal result = decimal.Round(i / conversionFactor, 4);
            if (result * conversionFactor == i)
            {
                return result;
            }
        }

        return i;
    }
David B
thanks, that way is way better than mine
Fredou
A: 

so if I understood correctly, any argument ARG which comes with a fraction <> 0 will stay as is and expressed in mg ... this should be easy to code

If ARG <> Int(ARG) Then
   ' Return ARG
Endif

anything without a fraction should be converted to the most appropriate unit (mg, g, dag, kg, t, etc). So for these we need to to look at the argument as a string and count the "0"es from the back-end and see from there how often we may divide by 10 without loosing numeric precision.

In your first example we count 6 Zeroes from the backend, so we could safely divide by 10^(6+4) to get not more than 4 fractional digits (which is more than we actually need).

In your last example we count 2 "0"es from the back, so we can safely divide by 10^(2+4).

So if the "save division power" is >=6, divide by 10^6 and express in kg if the "save division power is between 5 and 3, divide by 10^3 and express in g, below 3 leave as mg.

Hope that helps.

MikeD