Scale decimal value with a power of 10

What is the best way to scale a System.Decimal value with a capacity of 10 when the number of places is known?

value * (decimal)Math.Pow(10, places) comes to mind, but it suffers from two drawbacks:

  • It introduces floating point, which makes it difficult to determine rounding errors as numbers become large.

  • It seems like overkill to the point where all you are trying to do is change the simple scale component already encoded in the decimal data structure.

Is there a better way?

+4
source share
3 answers

You can create a table with dimensions 10, for example:

var pow10s = new int [] { 1, 10, 100, 1000, 10000, 100000, ... };

And then use the places as an index in this table:

return value * pow10s[place]

Update: , - , N , , :

public class Power10Scale
{
    private static readonly int[] Pow10s = {
        1, 10, 100, 1000, 10000, 100000,
    };

    public static int Up(int value, int places)
    {
        return Scale(value, places, (x, y) => x * y);
    }

    public static int Down(int value, int places)
    {
        return Scale(value, places, (x, y) => x / y);
    }

    private static int Scale(int value, int places, Func<int, int, int> operation)
    {
        if (places < Pow10s.Length)
            return operation(value, Pow10s[places]);

        return Scale(
            operation(value, Pow10s[Pow10s.Length - 1]),
            places - (Pow10s.Length - 1),
            operation);
    }
}
+3

. : , . 0, - , .

, .

1e + 28, :

public static decimal Scale(decimal number, int places)
{
    const byte MaxDivisorExponent = 28;
    const int e28_low = 268435456;
    const int e28_middle = 1042612833;
    const int e28_high = 542101086;
    var power = new Decimal(e28_low, e28_middle, e28_high, false, (byte)(MaxDivisorExponent - places));

    return number * power;
}

- , , 96- 1 + 28 Decimal.GetBits().

, .

+1

decimal.Parse, , Enumerable.Repeat.

, , - scale 28, ,

Scale(1e-28m, +56) == 1e+28
Scale(1e+28m, -56) == 1e-28

decimal.Parse ( ​​ decimal).


:

.

/// <summary>
/// Scales value to move the decimal point by a certain number of places
/// </summary>
public static decimal Scale(decimal value, int places)
{
    // Handle degenerate case
    if ( value == 0 )
        return 0;

    // Handle the case when the power of ten will overflow.
    // Split the problem up into two calls to Scale.
    if ( Math.Abs(places) > 28 )
    {
        var intermediateNumberOfPlaces = places / 2;
        var intermediateValue = Scale(value, intermediateNumberOfPlaces);
        return Scale(intermediateValue, places - intermediateNumberOfPlaces);
    }

    // Normal cases
    var powerOfTen = getPowerOfTen(Math.Abs(places));
    if ( places > 0 )
        return value * powerOfTen;

    return value / powerOfTen;
}

private static ConcurrentDictionary<int, decimal> powersOfTen = new ConcurrentDictionary<int, decimal>();

private static decimal getPowerOfTen(int power)
{
    return powersOfTen.GetOrAdd(power, p =>
    {
        var powerAsString = "1" + string.Concat(Enumerable.Repeat("0", p));
        return decimal.Parse(powerAsString, CultureInfo.InvariantCulture);
    });
}

This method will handle cases like scale(1e-28m, 56)or scale(1e28m, -56), which (although probably not many) are likely to be taken into account.


Tests for scale:

Here are the validation tests I used to write the code:

Assert.AreEqual(1, Scale(1, 0), "Invariant scale failed");
Assert.AreEqual(0, Scale(0, 100), "Scale of 0 failed");

Assert.AreEqual(100, Scale(1, 2), "Scale(1, 2) failed");
Assert.AreEqual(0.01, Scale(1, -2), "Scale(1, -2) failed");

Assert.AreEqual(1, Scale(0.01m, 2), "Scale(0.01, 2) failed");
Assert.AreEqual(1, Scale(100, -2), "Scale(100, -2) failed");

var large = Scale(1, 28);
var small = Scale(1, -28);

var shouldBeLarge = Scale(small, 56);
var shouldBeSmall = Scale(large, -56);

Assert.AreEqual(large, shouldBeLarge, "scaling 1e-28 by 56 failed");
Assert.AreEqual(small, shouldBeSmall, "scaling 1e28 by -56 failed");
0
source

Source: https://habr.com/ru/post/1676189/


All Articles