.net 将十进制值按10的幂进行缩放

.net 将十进制值按10的幂进行缩放,.net,decimal,.net,Decimal,什么是缩放系统的最佳方式。当已知位数时,十进制值以10的幂进行缩放 我想到了value*(十进制)Math.Pow(10位),但它有两个缺点: 它引入了浮点数,这使得随着数字越来越大,舍入误差很难解释 当您试图做的只是更改十进制数据结构中已编码的简单比例分量时,执行幂运算似乎有些过分 有更好的方法吗?您可以创建一个10次幂的表,如: var pow10s = new int [] { 1, 10, 100, 1000, 10000, 100000, ... }; 然后使用位置作为此表的索引:

什么是缩放系统的最佳方式。当已知位数时,十进制值以10的幂进行缩放

我想到了
value*(十进制)Math.Pow(10位)
,但它有两个缺点:

  • 它引入了浮点数,这使得随着数字越来越大,舍入误差很难解释

  • 当您试图做的只是更改十进制数据结构中已编码的简单比例分量时,执行幂运算似乎有些过分


  • 有更好的方法吗?

    您可以创建一个10次幂的表,如:

    var pow10s = new int [] { 1, 10, 100, 1000, 10000, 100000, ... };
    
    然后使用位置作为此表的索引:

    return value * pow10s[place]
    
    更新: 如果您不想在尝试索引数组超过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);
        }
    }
    
    public类Power10Scale
    {
    专用静态只读int[]Pow10s={
    1, 10, 100, 1000, 10000, 100000,
    };
    公共静态整型(整型值,整型位置)
    {
    返回刻度(值,位置,(x,y)=>x*y);
    }
    公共静态整型向下(整型值,整型位置)
    {
    返回刻度(值、位置,(x,y)=>x/y);
    }
    专用静态整数比例(整数值、整数位置、Func操作)
    {
    如果(位置<功率长度)
    返回操作(值,功率10s[位置]);
    回报率(
    操作(值,功率10s[功率10s.长度-1]),
    位置-(功率10s.长度-1),
    操作);
    }
    }
    
    直接使用小数的小数部分并没有简单的方法。它代表除数指数:你需要从中减去它来获得你想要的行为。值通常为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;
    }
    
    构造函数中的常量是低、中、高整数部分,它们构成十进制1e+28的96位尾数,只需调用即可获得

    但我不确定我是否愿意在生产中看到这一点。

    您可以使用从使用制作的字符串中获得10的幂

    需要注意的一个特殊情况是
    刻度的绝对值超过
    28
    ,这应该起作用,例如

    Scale(1e-28m, +56) == 1e+28
    Scale(1e+28m, -56) == 1e-28
    
    因为调用
    decimal.Parse
    将失败(中间幂不能用
    decimal
    值表示)


    代码: 我对下面的代码进行了修改


    我甚至可以从.NET源代码中复制和粘贴,只是为了让我感觉更好——或者更糟?:-)请记住不要将9以上的任何内容作为
    places
    :)您可以,我不认为复制表是特别糟糕的。出于兴趣,我可能还会尝试允许overflowSee,为什么需要这种方法?我正在将一个Android应用程序移植到Xamarin.Forms。该应用程序用于与后端对话,并将十进制值编码为缩放的Int32。对于某些值,刻度是可变的,并以位数的形式传输。在Android上,通过直接支持按位置缩放。
    /// <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);
        });
    }
    
    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");