1

I would like to find the least significant digit position of C# decimal and set a 1 in that position and zero in all other positions.

So for example for 2m the result would be 1m. For 34m the result would be 1m. For 0.4m it would return 0.1m. For 1200m I would want 100m.

How do I do this?

Can I use something similar to https://stackoverflow.com/a/757266/246622 ?

(Edit: I removed the confusing 2.0m, added result for 1200)

Community
  • 1
  • 1
tomsv
  • 7,207
  • 6
  • 55
  • 88

3 Answers3

2

You could take advantage of an implementation detail of Decimal, its exponent is equal to the number of significant digits in the fraction. You can obtain the value of the exponent with the GetBits() method. Which makes this (rather obscure) code work:

    public static Decimal SignificantFraction(Decimal d) {
        var b = decimal.GetBits(d);
        return new decimal(1, 0, 0, false, (byte)((b[3] >> 16) & 0x7fff));
    }

Note that this even works in corner-cases like 0.0m, it produces 0.1m. You didn't specify what should happen to negative values so I punted for false in the constructor. Replace it with d < 0 if you want the result to be negative.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    While this clever trick would definitely work for the normalized decimals, some tricky corner cases would break it. For example, if you set `d` to `new decimal(100000,0,0,false,5)`, which is a valid but grossly unorthodox representation of `1.0M`, your program would produce `0.00001` ([link to a demo](http://ideone.com/XMw5c8)). – Sergey Kalinichenko Feb 25 '13 at 17:13
  • Of course there's no cure for garbage-in, garbage-out. You cannot make a reasonable assertion that such a number contains *any* significant digits. – Hans Passant Feb 25 '13 at 17:16
  • Leaving aside a rather debatable "garbageness" level of a `100000*10^-5` representation, a more defensive algorithm would definitely provide an adequate cure to the garbage in - garbage out issue here. The efficiency would suffer, though. – Sergey Kalinichenko Feb 25 '13 at 17:24
  • Your point becomes valid if you considered zeros in numbers such as `1.00` significant (.NET definitely treats them as significant, assigning different representations to `1M`, `1.0M`, and `1.000M`). Unfortunately, the last edit of the question has invalidated all answers on the page :( – Sergey Kalinichenko Feb 25 '13 at 17:40
  • I like this approach because of the efficiency but for values without decimals such as 1200m it returns 1m instead of 100m as I had hoped for. – tomsv Feb 25 '13 at 17:47
  • This makes no sense, 1200m has 4 significant digits. If you claim it has 2 then you need to show us how you write 1200 with 3 significant digits. The only way to get 1200 with 2 sigdig is to write 12m*100. – Hans Passant Feb 25 '13 at 18:03
1

How about something along these lines?

        decimal number = 1200m;

        decimal result = 1;
        decimal tmp = number;
        while (tmp % 1 != 0)
        {
            result /= 10;
            tmp *= 10;
        }
        if (result > 0 && number != 0)
        {
            tmp = number;
            while (tmp % 10 == 0)
            {
                result *= 10;
                tmp /= 10;
            }
        }

Edit: So with the 1200 example added my solution is not as neat anymore... But I think it should do the trick

Evelie
  • 2,919
  • 2
  • 14
  • 21
  • Returns 1. I think thats what OP is after since 34m the result should be 1 aswel – Evelie Feb 25 '13 at 16:55
  • I'd have guessed that the OP considers `100` the answer for an input of `1200`. Perhaps we'll hear from them. – HABO Feb 25 '13 at 16:56
  • That was my initial thought aswel. But 2m -> 1m and 34m -> 1m makes me believe he wants 1200 to be 1m too. – Evelie Feb 25 '13 at 17:01
  • If you take the question to be a matter of locating the _least-significant non-zero digit_ then the answer moves to the left: `3.1415` becomes `0.0001`, `0.0042` is also `0.0001`, `24.` is `1.`, `360.` is `10.`, and on up we go. – HABO Feb 25 '13 at 17:23
  • Ye he updated the original question. Working on a replacement. Hopefully this works better – Evelie Feb 25 '13 at 17:27
0
        var t = 1.11111000m;
        //var t = 134000m;

        var tr = t.ToString().Reverse().ToArray();
        char[] ca;
        if (t % 1 > 0)
            ca = tr.SkipWhile(c => c == '0').Select((c, i) => i == 0 ? '1' : c == ',' ? ',' : '0').Reverse().ToArray();
        else
        {
            ca = tr.TakeWhile((c, i) => c == '0' || tr[i - 1] == '0').Reverse().ToArray();
            ca[0] = '1';
        }

        var result = decimal.Parse(new string(ca));
aush
  • 2,108
  • 1
  • 14
  • 24