定点数运算(乘以10000方式)(二)

继续上一篇的内容:传送门

1.显式/隐式转换
在项目内无缝接入定点数,就需要支持其与float、int等常用类型进行计算

如下演示:

float a = 0.1f;
int b =  3;
float result = a * b;

//如果支持转换的话,可以快速修改代码

FP a = (FP) 0.1f;//(FP) 是编辑器自动加的,写出来只是为了演示
int b = 3;
FP result = a * b;

这里逻辑比较简单,但是如果是项目内复杂的逻辑,只需要把【float】替换成【FP】就好了,不需要做额外的处理,还是很方便的

那么如何做转换呢?
代码如下:

public static implicit operator FP(float value)
{
    return new FP(value);
}
public static explicit operator float(FP value)
{
    return (float) value.rawValue / CARDINAL_NUMBER;
}
public static implicit operator FP(int value)
{
    return new FP(value);
}
public static explicit operator int(FP value)
{
    return value.rawValue / CARDINAL_NUMBER;
}

2.重载加减乘除操作符

重载操作符就是支持把struct类型的FP,当成普通的int/float一样,直接可以走正常的计算公式,用起来十分方便

public static FP operator +(FP x, FP y)
{
    FP result = default;
    result.rawValue = x.rawValue + y.rawValue;
    return result;
}
public static FP operator -(FP x, FP y)
{
    FP result = default;
    result.rawValue = x.rawValue - y.rawValue;
    return result;
}
/// <summary>
/// 两数相乘,需转换成long防止超出int上限
/// </summary>
public static FP operator *(FP x, FP y)
{
    FP result = default;
    result.rawValue = (int) ((x.rawValue * (long) y.rawValue) / CARDINAL_NUMBER);
    return result;
}
public static FP operator /(FP x, FP y)
{
    if (y.rawValue == 0)
    {
        return FP.infinity;
    }
    FP result = default;
    result.rawValue = (int) (((long) x.rawValue * CARDINAL_NUMBER) / (long) y.rawValue);
    return result;
}
public static FP operator -(FP x)
{
    FP result = default;
    result.rawValue = -x.rawValue;
    return result;
}
public static bool operator ==(FP x, FP y)
{
    return x.rawValue == y.rawValue;
}
public static bool operator !=(FP x, FP y)
{
    return x.rawValue != y.rawValue;
}
public static bool operator >(FP x, FP y)
{
    return x.rawValue > y.rawValue;
}
public static bool operator <(FP x, FP y)
{
    return x.rawValue < y.rawValue;
}
public static bool operator >=(FP x, FP y)
{
    return x.rawValue >= y.rawValue;
}
public static bool operator <=(FP x, FP y)
{
    return x.rawValue <= y.rawValue;
}

3.数学函数相关计算(绝对值,开方,三角函数等)

绝对值比较简单,如果是负数直接取反即可
其中 [MethodImpl(MethodImplOptions.AggressiveInlining)] 可以编译成内联函数,优化调用速度

/// <summary>
/// 取绝对值
/// </summary>
/// <param name="x">某个定点数</param>
/// <returns>返回定点数</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FP Abs(FP x)
{
    if (x.rawValue < 0)
    {
        x.rawValue = -x.rawValue;
    }
    return x;
}

开方,一般使用二分or牛顿迭代,这里使用的是牛顿迭代法

public static FP Sqrt(FP n)
{
    if (n <= zero)
    {
        //负数时,Mathf.Sqrt返回的是原来数,这里返回的0
        return zero;
    }
    long x = n.rawValue;
    if (x < INFINITY)
    {
        x *= CARDINAL_NUMBER;
    }
    long result = x;
    long temp = result - 1;
    while (temp < result)
    {
        result = temp;
        temp = (result + (x / result)) >> 1;
    }
    return new FP((int) result, true);
}

三角函数,一般都是通过查表法计算的,因为效率高

什么是查表法?
就拿 Sin 函数举例,如图所示:

可以看出Sin函数曲线是循环往复的,0-2PI 就是一个周期
对于定点数,0-2PI有多少个数是确定的,因为小数点是确定的
根据我们的计算规则,2PI表示的数等于【0-62830】,换算成小数就是【0-6.2830】

也就是说,我们只需要分别算出【0-6.2830】也就是62830个数计算的结果存起来,计算的时候直接通过table查表映射到结果就可以了!

同时可以观察到,只计算 0-PI/2 的区间就可以了,其他的通过取反或者1-x就可以得出结果

那么生成代码如下:
会生成一个文件,文件内就表示Sin的结果

internal static void GenerateSinTable()
{
    using (var writer = new StreamWriter(GenCSFileDir + "FPSinTable.cs"))
    {
        writer.Write(
            @"
space BattleCore.SimpleMath
public partial struct FP
{
    public static readonly int[] sinTable = new[] {");
        int lineCounter = 0;
        for (int i = 0; i < HALF_PI; ++i)
        {
            if (lineCounter++ % 8 == 0)
            {
                writer.WriteLine();
                writer.Write("        ");
            }
            float f = i * 1f / CARDINAL_NUMBER;
            float sin = Mathf.Sin(f);
            int rawValue = (int) (sin * CARDINAL_NUMBER);
            writer.Write("{0}, ", rawValue);
        }
        writer.Write(
            @"
        };
}
    }
    AssetDatabase.Refresh();
    AssetDatabase.SaveAssets();
}

生成的文件如下所示:

public partial struct FP
{
    public static readonly int[] sinTable = new[] {
    0, 0, 1, 3, 3, 5, 5, 6, 
    7, 8, 9, 10, 11, 12, 13, 14, 
    15, 16, 17, 18, 19, 20, 21, 22, 
    23, 24, 25, 26, 27, 28, 29, 30, 
    31, 32, 33, 34, 35, 36, 37, 38, 
    39, 40, 41, 42, 43, 44, 45, 46, 
    47, 48, 49, 50, 51, 52, 53, 54, 
    55, 56, 57, 58, 59, 60, 61, 62, 
    63, 64, 65, 66, 67, 68, 69, 70, 
    ...

如何取值计算呢?

private const int PI = 31415;
private const int TWO_PI = 62830;
private const int HALF_PI = 15707;

 /// <summary>
 /// 求正弦函数值,通过查表法(生成代码在FPTrigonometricGenerator)
 /// 为了节约生成的数组内存大小,只取了0-PI/2的值,其他的通过简单计算获得
 /// </summary>
 /// <param name="x">弧度</param>
 /// <returns>返回正弦函数值</returns>
 public static FP Sin(FP x)
 {
     int raw = x.rawValue % TWO_PI;
     if (raw < 0)
         raw += TWO_PI;
     int p1 = raw % HALF_PI;
     int p2 = raw / HALF_PI;
     if (p2 == 0)
         return new FP {rawValue = sinTable[p1]};
     else if (p2 == 1)
         return new FP {rawValue = sinTable[HALF_PI - 1 - p1]};
     else if (p2 == 2)
         return new FP {rawValue = -sinTable[p1]};
     else
         return new FP {rawValue = -sinTable[HALF_PI - 1 - p1]};
 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值