【问题标题】:How to make multiplication with bit shift in C#如何在 C# 中使用位移位进行乘法运算
【发布时间】:2012-12-24 18:13:18
【问题描述】:

我知道在定点数学运算中使用位移乘法,例如,如果我需要将两个浮点值相乘,我应该将其乘以比例因子(例如在这种情况下为 20),然后,我应该将结果相乘值作为整数值,然后我应该将它们返回到数字的正常表示,应该再次按比例因子除以如何通过位移操作执行该操作?

基于this 文章:5.4 定点算法

我已经尝试了下面的代码示例,我希望结果 floatResShiftfloatResNormal 会相同但它们不同,我做错了什么?:

        float mul1 = 18.579434f;
        float mul2 = 34.307951f;

        int shiftMul1 = (int)((2 ^ 32) * mul1);
        int shiftMul2 = (int)((2 ^ 32) * mul2);

        var resultMul = shiftMul1 * shiftMul2;
        float floatResShift = resultMul >> 32; // wrong value
        float floatResNormal = mul1 * mul2; //expected value

更新:

定点算术解释:

当 A = 时,使用定点算法计算 A · B 的结果 2.5 和 B = 8.4 使用 32 位整数将涉及以下操作:

确定比例因子。这在很大程度上取决于什么样的 很可能会看到数字。因为这个例子中的数字是这样的 低,它不太重要,和 16 个小数位(位到右边 小数点)是可以接受的。比例因子将为 f = 216 = 65536。这种格式称为 Q15.16(小数点左侧 15 位,右侧 16 位,符号位)。

使用普通整数乘法将 Ai 和 Bi 相乘。 Ri = Ai · Bi = 163840 · 550502 = 90194247680。数量如此之大的原因是Ai和Bi都被缩放到我们的 Q15.16 格式,所以乘法的结果本质上是 (A · f) · (B · f) = A·B·f2。

为了将我们的结果带回 Q15.16 格式,结果 因此必须除以比例因子。这也可以使用 位移算法,但为了简单起见,这里使用除法。 Ri/f = 90194247680/65536 = 1376255 这是我们在 Q15.16 中的结果 格式

要将数字变回正常的实数,只需 将其转换为所需的格式并除以缩放因子 再次,所以: 1376255.0/65536.0 = 20.999985 接近预期 21 号。

使用比例因子缩放数字。在二进制算术中,这可以 使用位移来完成,但为简单起见,我们将使用 乘以比例因子。 Ai = A·f = 2.5·65536 = 163840 和 B · f = 8.4 · 65536 = 550502.4 然后截断它 成整数,所以 Bi = 550502。

要将数字变回正常的实数,只需 将其转换为所需的格式并除以缩放因子 再次,所以: 1376255.0/65536.0 = 20.999985 接近预期 21 号。

如何在上面的评论中制作相同的内容,但要使用位移位。并且在点之后具有很大的浮点值

我尝试过上面的代码,但没有成功。

例如,我需要将两个值 18.579434f 和 34.307951f 相乘,但要使用定点算法。

更新:

我用较小的比例因子尝试过这个,但没有运气。

解决方案:

也许我没有清楚地解释问题,但我解决了问题并找到了解决方案:

谢谢大家,问题已经结束,这里是定点乘法的完整代码:

    float mul1 = 18.579434f;
    float mul2 = 34.307951f;

    int scaleFactor = (int) Math.Pow(2, 20);

    long shiftMul1 = (int)((scaleFactor) * mul1);
    long shiftMul2 = (int)((scaleFactor) * mul2);

    var resultMul = shiftMul1 * shiftMul2;
    float floatResShift = resultMul >> 40; 
    float floatResNormal = mul1 * mul2; // the result floatResNormal almost same as floatResShift

【问题讨论】:

  • 看中间值。
  • 中间值有什么问题?
  • 我已经更新了!看看最后两行的 cmets。
  • 你乘以两次 2^32... 正如 pst 所说,它应该是 2
  • 假设您解决了这些问题并让代码按照您的想法执行,您将对超出该范围的数字使用 0.32 固定点。还有,为什么?浮点乘法只比整数乘法慢一点,你正在做的所有其他事情都会立即破坏优势。

标签: c# multiplication bit-shift


【解决方案1】:
var k = 20;
var k_2 = k/2;
var p = 1 << k;

float mul1 = 18.579434f;
float mul2 = 34.307951f;

int shiftMul1 = (int)(p * mul1);
int shiftMul2 = (int)(p * mul2);

//fixed point multiplication         
var resultMul = ((shiftMul1 >> k_2) * (shiftMul2 >> k_2));

float floatResShift = ((float)resultMul)/p;
float floatResNormal = mul1 * mul2; 

Console.WriteLine("{0} {1}", floatResNormal, floatResShift);

输出:

637,4223 637,4043

【讨论】:

    【解决方案2】:

    检查此代码

    /*
    Fixed Point Arithmatic structure and relevant methods. Simple fixed point structure included as well.
    
    Created from information and code gathered here: http://stackoverflow.com/questions/605124/fixed-point-math-in-c
    
    May be used for anything without permission.
    
    To quote the original author (x4000 of stackoverflow.com):
    "The accuracy of these functions as they are coded here is more than enough for my purposes, but if you need more you can increase the SHIFT AMOUNT on FInt.
    Just be aware that if you do so, the constants on [trigonomic] functions will then need to be divided by 4096 and then multiplied by whatever your new SHIFT AMOUNT requires.
    You're likely to run into some bugs if you do that and aren't careful, so be sure to run checks against the built-in Math functions to make sure that your results aren't
    being put off by incorrectly adjusting a constant."
    
    Code credit: x4000 of stackoverflow.com
    
    Compiled into a usable source file by: Paul Bergeron
    
    Date: 7/1/2009
    
    More fixed point functions can be found written in Java here: http://home.comcast.net/~ohommes/MathFP/
    
    */
    
    public struct FInt
    {
        public long RawValue;
        public const int SHIFT_AMOUNT = 12; //12 is 4096
    
        public const long One = 1 << SHIFT_AMOUNT;
        public const int OneI = 1 << SHIFT_AMOUNT;
        public static FInt OneF = new FInt( 1, true );
    
        #region Constructors
        public FInt( long StartingRawValue, bool UseMultiple )
        {
            this.RawValue = StartingRawValue;
            if ( UseMultiple )
                this.RawValue = this.RawValue << SHIFT_AMOUNT;
        }
        public FInt( double DoubleValue )
        {
            DoubleValue *= (double)One;
            this.RawValue = (int)Math.Round( DoubleValue );
        }
        #endregion
    
        public int IntValue
        {
            get { return (int)( this.RawValue >> SHIFT_AMOUNT ); }
        }
    
        public int ToInt()
        {
            return (int)( this.RawValue >> SHIFT_AMOUNT );
        }
    
        public double ToDouble()
        {
            return (double)this.RawValue / (double)One;
        }
    
        public FInt Inverse
        {
            get { return new FInt( -this.RawValue, false ); }
        }
    
        #region FromParts
        /// <summary>
        /// Create a fixed-int number from parts.  For example, to create 1.5 pass in 1 and 500.
        /// </summary>
        /// <param name="PreDecimal">The number above the decimal.  For 1.5, this would be 1.</param>
        /// <param name="PostDecimal">The number below the decimal, to three digits.
        /// For 1.5, this would be 500. For 1.005, this would be 5.</param>
        /// <returns>A fixed-int representation of the number parts</returns>
        public static FInt FromParts( int PreDecimal, int PostDecimal )
        {
            FInt f = new FInt( PreDecimal );
            if ( PostDecimal != 0 )
                f.RawValue += ( new FInt( PostDecimal ) / 1000 ).RawValue;
    
            return f;
        }
        #endregion
    
        #region *
        public static FInt operator *( FInt one, FInt other )
        {
            return new FInt( ( one.RawValue * other.RawValue ) >> SHIFT_AMOUNT, false );
        }
    
        public static FInt operator *( FInt one, int multi )
        {
            return one * (FInt)multi;
        }
    
        public static FInt operator *( int multi, FInt one )
        {
            return one * (FInt)multi;
        }
        #endregion
    
        #region /
        public static FInt operator /( FInt one, FInt other )
        {
            return new FInt( ( one.RawValue << SHIFT_AMOUNT ) / ( other.RawValue  ), false );
        }
    
        public static FInt operator /( FInt one, int divisor )
        {
            return one / (FInt)divisor;
        }
    
        public static FInt operator /( int divisor, FInt one )
        {
            return (FInt)divisor / one;
        }
        #endregion
    
        #region %
        public static FInt operator %( FInt one, FInt other )
        {
            return new FInt( ( one.RawValue ) % ( other.RawValue ), false );
        }
    
        public static FInt operator %( FInt one, int divisor )
        {
            return one % (FInt)divisor;
        }
    
        public static FInt operator %( int divisor, FInt one )
        {
            return (FInt)divisor % one;
        }
        #endregion
    
        #region +
        public static FInt operator +( FInt one, FInt other )
        {
            return new FInt( one.RawValue + other.RawValue, false );
        }
    
        public static FInt operator +( FInt one, int other )
        {
            return one + (FInt)other;
        }
    
        public static FInt operator +( int other, FInt one )
        {
            return one + (FInt)other;
        }
        #endregion
    
        #region -
        public static FInt operator -( FInt one, FInt other )
        {
            return new FInt( one.RawValue - other.RawValue, false );
        }
    
        public static FInt operator -( FInt one, int other )
        {
            return one - (FInt)other;
        }
    
        public static FInt operator -( int other, FInt one )
        {
            return (FInt)other - one;
        }
        #endregion
    
        #region ==
        public static bool operator ==( FInt one, FInt other )
        {
            return one.RawValue == other.RawValue;
        }
    
        public static bool operator ==( FInt one, int other )
        {
            return one == (FInt)other;
        }
    
        public static bool operator ==( int other, FInt one )
        {
            return (FInt)other == one;
        }
        #endregion
    
        #region !=
        public static bool operator !=( FInt one, FInt other )
        {
            return one.RawValue != other.RawValue;
        }
    
        public static bool operator !=( FInt one, int other )
        {
            return one != (FInt)other;
        }
    
        public static bool operator !=( int other, FInt one )
        {
            return (FInt)other != one;
        }
        #endregion
    
        #region >=
        public static bool operator >=( FInt one, FInt other )
        {
            return one.RawValue >= other.RawValue;
        }
    
        public static bool operator >=( FInt one, int other )
        {
            return one >= (FInt)other;
        }
    
        public static bool operator >=( int other, FInt one )
        {
            return (FInt)other >= one;
        }
        #endregion
    
        #region <=
        public static bool operator <=( FInt one, FInt other )
        {
            return one.RawValue <= other.RawValue;
        }
    
        public static bool operator <=( FInt one, int other )
        {
            return one <= (FInt)other;
        }
    
        public static bool operator <=( int other, FInt one )
        {
            return (FInt)other <= one;
        }
        #endregion
    
        #region >
        public static bool operator >( FInt one, FInt other )
        {
            return one.RawValue > other.RawValue;
        }
    
        public static bool operator >( FInt one, int other )
        {
            return one > (FInt)other;
        }
    
        public static bool operator >( int other, FInt one )
        {
            return (FInt)other > one;
        }
        #endregion
    
        #region <
        public static bool operator <( FInt one, FInt other )
        {
            return one.RawValue < other.RawValue;
        }
    
        public static bool operator <( FInt one, int other )
        {
            return one < (FInt)other;
        }
    
        public static bool operator <( int other, FInt one )
        {
            return (FInt)other < one;
        }
        #endregion
    
        public static explicit operator int( FInt src )
        {
            return (int)( src.RawValue >> SHIFT_AMOUNT );
        }
    
        public static explicit operator FInt( int src )
        {
            return new FInt( src, true );
        }
    
        public static explicit operator FInt( long src )
        {
            return new FInt( src, true );
        }
    
        public static explicit operator FInt( ulong src )
        {
            return new FInt( (long)src, true );
        }
    
        public static FInt operator <<( FInt one, int Amount )
        {
            return new FInt( one.RawValue << Amount, false );
        }
    
        public static FInt operator >>( FInt one, int Amount )
        {
            return new FInt( one.RawValue >> Amount, false );
        }
    
        public override bool Equals( object obj )
        {
            if ( obj is FInt )
                return ( (FInt)obj ).RawValue == this.RawValue;
            else
                return false;
        }
    
        public override int GetHashCode()
        {
            return RawValue.GetHashCode();
        }
    
        public override string ToString()
        {
            return this.RawValue.ToString();
        }
    
        #region PI, DoublePI
        public static FInt PI = new FInt( 12868, false ); //PI x 2^12
        public static FInt TwoPIF = PI * 2; //radian equivalent of 260 degrees
        public static FInt PIOver180F = PI / (FInt)180; //PI / 180
        #endregion
    
        #region Sqrt
        public static FInt Sqrt( FInt f, int NumberOfIterations )
        {
            if ( f.RawValue < 0 ) //NaN in Math.Sqrt
                throw new ArithmeticException( "Input Error" );
            if ( f.RawValue == 0 )
                return (FInt)0;
            FInt k = f + FInt.OneF >> 1;
            for ( int i = 0; i < NumberOfIterations; i++ )
                k = ( k + ( f / k ) ) >> 1;
    
            if ( k.RawValue < 0 )
                throw new ArithmeticException( "Overflow" );
            else
                return k;
        }
    
        public static FInt Sqrt( FInt f )
        {
            byte numberOfIterations = 8;
            if ( f.RawValue > 0x64000 )
                numberOfIterations = 12;
            if ( f.RawValue > 0x3e8000 )
                numberOfIterations = 16;
            return Sqrt( f, numberOfIterations );
        }
        #endregion
    
        #region Sin
        public static FInt Sin( FInt i )
        {
            FInt j = (FInt)0;
            for ( ; i < 0; i += new FInt( 25736, false ) ) ;
            if ( i > new FInt( 25736, false ) )
                i %= new FInt( 25736, false );
            FInt k = ( i * new FInt( 10, false ) ) / new FInt( 714, false );
            if ( i != 0 && i != new FInt( 6434, false ) && i != new FInt( 12868, false ) &&
                i != new FInt( 19302, false ) && i != new FInt( 25736, false ) )
                j = ( i * new FInt( 100, false ) ) / new FInt( 714, false ) - k * new FInt( 10, false );
            if ( k <= new FInt( 90, false ) )
                return sin_lookup( k, j );
            if ( k <= new FInt( 180, false ) )
                return sin_lookup( new FInt( 180, false ) - k, j );
            if ( k <= new FInt( 270, false ) )
                return sin_lookup( k - new FInt( 180, false ), j ).Inverse;
            else
                return sin_lookup( new FInt( 360, false ) - k, j ).Inverse;
        }
    
        private static FInt sin_lookup( FInt i, FInt j )
        {
            if ( j > 0 && j < new FInt( 10, false ) && i < new FInt( 90, false ) )
                return new FInt( SIN_TABLE[i.RawValue], false ) +
                    ( ( new FInt( SIN_TABLE[i.RawValue + 1], false ) - new FInt( SIN_TABLE[i.RawValue], false ) ) /
                    new FInt( 10, false ) ) * j;
            else
                return new FInt( SIN_TABLE[i.RawValue], false );
        }
    
        private static int[] SIN_TABLE = {
            0, 71, 142, 214, 285, 357, 428, 499, 570, 641,
            711, 781, 851, 921, 990, 1060, 1128, 1197, 1265, 1333,
            1400, 1468, 1534, 1600, 1665, 1730, 1795, 1859, 1922, 1985,
            2048, 2109, 2170, 2230, 2290, 2349, 2407, 2464, 2521, 2577,
            2632, 2686, 2740, 2793, 2845, 2896, 2946, 2995, 3043, 3091,
            3137, 3183, 3227, 3271, 3313, 3355, 3395, 3434, 3473, 3510,
            3547, 3582, 3616, 3649, 3681, 3712, 3741, 3770, 3797, 3823,
            3849, 3872, 3895, 3917, 3937, 3956, 3974, 3991, 4006, 4020,
            4033, 4045, 4056, 4065, 4073, 4080, 4086, 4090, 4093, 4095,
            4096
        };
        #endregion
    
        private static FInt mul( FInt F1, FInt F2 )
        {
            return F1 * F2;
        }
    
        #region Cos, Tan, Asin
        public static FInt Cos( FInt i )
        {
            return Sin( i + new FInt( 6435, false ) );
        }
    
        public static FInt Tan( FInt i )
        {
            return Sin( i ) / Cos( i );
        }
    
        public static FInt Asin( FInt F )
        {
            bool isNegative = F < 0;
            F = Abs( F );
    
            if ( F > FInt.OneF )
                throw new ArithmeticException( "Bad Asin Input:" + F.ToDouble() );
    
            FInt f1 = mul( mul( mul( mul( new FInt( 145103 >> FInt.SHIFT_AMOUNT, false ), F ) -
                new FInt( 599880 >> FInt.SHIFT_AMOUNT, false ), F ) +
                new FInt( 1420468 >> FInt.SHIFT_AMOUNT, false ), F ) -
                new FInt( 3592413 >> FInt.SHIFT_AMOUNT, false ), F ) +
                new FInt( 26353447 >> FInt.SHIFT_AMOUNT, false );
            FInt f2 = PI / new FInt( 2, true ) - ( Sqrt( FInt.OneF - F ) * f1 );
    
            return isNegative ? f2.Inverse : f2;
        }
        #endregion
    
        #region ATan, ATan2
        public static FInt Atan( FInt F )
        {
            return Asin( F / Sqrt( FInt.OneF + ( F * F ) ) );
        }
    
        public static FInt Atan2( FInt F1, FInt F2 )
        {
            if ( F2.RawValue == 0 && F1.RawValue == 0 )
                return (FInt)0;
    
            FInt result = (FInt)0;
            if ( F2 > 0 )
                result = Atan( F1 / F2 );
            else if ( F2 < 0 )
            {
                if ( F1 >= 0 )
                    result = ( PI - Atan( Abs( F1 / F2 ) ) );
                else
                    result = ( PI - Atan( Abs( F1 / F2 ) ) ).Inverse;
            }
            else
                result = ( F1 >= 0 ? PI : PI.Inverse ) / new FInt( 2, true );
    
            return result;
        }
        #endregion
    
        #region Abs
        public static FInt Abs( FInt F )
        {
            if ( F < 0 )
                return F.Inverse;
            else
                return F;
        }
        #endregion
    
    }
    
    public struct FPoint
    {
        public FInt X;
        public FInt Y;
    
        public FPoint( FInt X, FInt Y )
        {
            this.X = X;
            this.Y = Y;
        }
    
        public static FPoint FromPoint( Point p )
        {
            FPoint f = new FPoint();
            f.X = (FInt)p.X;
            f.Y = (FInt)p.Y;
            return f;
        }
    
        public static Point ToPoint( FPoint f )
        {
            return new Point( f.X.IntValue, f.Y.IntValue );
        }
    }
    

    【讨论】:

    • 感谢代码,但如何使用该类,以及为什么它如此庞大,在我评论的文章中:In binary arithmetic this can be accomplished using bit shifts 是否有更短的解决方案。
    • 好吧,那个答案是解决一般不动点算术,我来总结一下
    猜你喜欢
    • 1970-01-01
    • 2011-04-12
    • 2018-02-25
    • 2023-03-07
    • 2011-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多