在 C# 中将 ulong 映射到 long?

Mapping a ulong to a long in C#?

我正在尝试将 ulong 映射到 long(反之亦然),并将 uint 映射到 int(反之亦然),如下所示 - 为了将值保存在 MS-SQL-仅具有带符号整数和双整数类型的数据库。

我这样做是因为我必须检查(在数据库中)一个数字(uint,ulong)是否在一堆 uint/ulong 范围(IPs - v4 & v6;实际上是 ulong实际上是由两个 ulong 组成的 uint128。

有没有比我这里的代码更有效的方法来完成这个:

public static ulong SignedLongToUnsignedLong(long signedLongValue)
{
    ulong backConverted = 0;

    // map ulong to long [ 9223372036854775808 = abs(long.MinValue) ]
    if (signedLongValue < 0)
    {
        // Cannot take abs from MinValue
        backConverted = (ulong)System.Math.Abs(signedLongValue - 1);
        backConverted = 9223372036854775808 - backConverted - 1;
    }
    else
    {
        backConverted = (ulong)signedLongValue;
        backConverted += 9223372036854775808;
    }

    return backConverted;
}


public static long UnsignedLongToSignedLong(ulong unsignedLongValue)
{
    // map ulong to long [ 9223372036854775808 = abs(long.MinValue) ]
    return (long) (unsignedLongValue - 9223372036854775808);
}


public static int UnsignedIntToSignedInt(uint unsignedIntValue)
{
    // map uint to int [ 2147483648 = abs(long.MinValue) ]
    return (int)(unsignedIntValue - 2147483648);
}


public static uint SignedIntToUnsignedInt(int signedIntValue)
{
    uint backConverted = 0;

    // map ulong to long [ 2147483648 = abs(long.MinValue) ]
    if (signedIntValue < 0)
    {
        // Cannot take abs from MinValue
        backConverted = (uint)System.Math.Abs(signedIntValue - 1);
        backConverted = 2147483648 - backConverted - 1;
    }
    else
    {
        backConverted = (uint)signedIntValue;
        backConverted += 2147483648;
    }

    return backConverted;
}


public static void TestLong()
{
    long min_long = -9223372036854775808;
    long max_long = 9223372036854775807;

    ulong min_ulong = ulong.MinValue; // 0
    ulong max_ulong = ulong.MaxValue; // 18446744073709551615  = (2^64)-1

    long dbValueMin = UnsignedLongToSignedLong(min_ulong);
    long dbValueMax = UnsignedLongToSignedLong(max_ulong);


    ulong valueFromDbMin = SignedLongToUnsignedLong(dbValueMin);
    ulong valueFromDbMax = SignedLongToUnsignedLong(dbValueMax);

    System.Console.WriteLine(dbValueMin);
    System.Console.WriteLine(dbValueMax);

    System.Console.WriteLine(valueFromDbMin);
    System.Console.WriteLine(valueFromDbMax);
}


public static void TestInt()
{
    int min_int = -2147483648; // int.MinValue
    int max_int = 2147483647; // int.MaxValue

    uint min_uint= uint.MinValue; // 0
    uint max_uint = uint.MaxValue; // 4294967295 = (2^32)-1


    int dbValueMin = UnsignedIntToSignedInt(min_uint);
    int dbValueMax = UnsignedIntToSignedInt(max_uint);

    uint valueFromDbMin = SignedIntToUnsignedInt(dbValueMin);
    uint valueFromDbMax = SignedIntToUnsignedInt(dbValueMax);

    System.Console.WriteLine(dbValueMin);
    System.Console.WriteLine(dbValueMax);

    System.Console.WriteLine(valueFromDbMin);
    System.Console.WriteLine(valueFromDbMax);
}

选项 1:保序映射

这听起来像是您要一张保留顺序的地图,也就是说,例如,如果 xyulongx < y , 然后 MapUlongToLong(x) < MapUlongToLong(y).

操作方法如下:

要从 ulong 映射到 long,请转换并添加 long.MinValue。要从 long 映射回 ulong,请减去 long.MinValue 并投射。在任何一种情况下,都使用未经检查的上下文,以便忽略溢出条件。

public static long MapUlongToLong(ulong ulongValue)
{
    return unchecked((long)ulongValue + long.MinValue);
}

public static ulong MapLongToUlong(long longValue)
{
    return unchecked((ulong)(longValue - long.MinValue));
}

uintint 的逻辑完全类似。

(选项1是我2016年写的原答案,我在2021年添加了选项2,以及两者的比较。)

选项 2:非保序映射

我认为这不是您要的,但如果您不关心保留顺序,则转换会更容易。

这些函数的工作方式与上述函数相同,只是我们不用费心去加减long.MinValue

public static long MapUlongToLong(ulong ulongValue)
{
    return unchecked((long)ulongValue);
}

public static ulong MapLongToUlong(long longValue)
{
    return unchecked((ulong)longValue);
}

哪个选项更好?

选项 1 保留顺序而选项 2 不保留顺序,因此如果您需要保留顺序,请使用选项 1。

选项 1 中的函数执行需要多长时间?好吧,这些函数可能会被 JIT 编译器内联和优化,它们最终要求 CPU 做一些非常非常简单的事情。我猜每个函数调用将花费不到 1 纳秒。

其中一条评论将这种不到一纳秒的执行时间描述为“相对较慢”。如果纳秒对您来说太慢,您可能需要使用选项 2。

选项 2 中的函数也可能会被 JIT 编译器内联和优化,事实证明,就 CPU 而言,这些函数几乎什么都不做。因此,不会为这些函数生成机器代码,因此每个函数调用根本不需要时间——换句话说,0 纳秒。

与选项 2 做同样的事情,我猜它也会 运行 同样快。

尽管 Tanner Swett 是正确的。一个更好但更脏的解决方案是告诉 .net 将对 ulong 的访问映射到与 long 相同的内存地址。这将为您提供瞬时转换速度。

void Main()
{
    var foo = new Foo { Long = -1 };

    Console.WriteLine(foo.ULong);
}

// Define other methods and classes here
[StructLayout(LayoutKind.Explicit)]
public class Foo
{
    [FieldOffset(0)]
    private ulong _ulong;

    [FieldOffset(0)]
    private long _long;

    public long Long
    {
        get { return _long; }
        set { _long = value; }
    }

    public ulong ULong
    {
        get { return _ulong; }
        set { _ulong = value; }
    }
}

通过将 entity framework POCO 设置为使用显示的属性,您可以控制字段映射到的内存地址。

因此,不会发生转换。

此代码比 Tanner Swett 的代码快 100%。

死灵法术。
基于 Tanner Swett 答案的通用答案:

private static class Number<T>
{

    private static object GetConstValue(System.Type t, string propertyName)
    {
        System.Reflection.FieldInfo pi = t.GetField(propertyName, System.Reflection.BindingFlags.Static
            | System.Reflection.BindingFlags.Public
            | System.Reflection.BindingFlags.NonPublic
            );

        return pi.GetValue(null);
    }

    private static T GetMinValue<T>()
    {
        return (T)GetConstValue(typeof(T), "MinValue");
    }

    private static T GetMaxValue<T>()
    {
        return (T)GetConstValue(typeof(T), "MaxValue");
    }


    private static System.Func<T, T, T> CompileAdd<T>()
    {
        // Declare the parameters
        System.Linq.Expressions.ParameterExpression paramA =
            System.Linq.Expressions.Expression.Parameter(typeof(T), "a");

        System.Linq.Expressions.ParameterExpression paramB =
            System.Linq.Expressions.Expression.Parameter(typeof(T), "b");

        // Add the parameters
        System.Linq.Expressions.BinaryExpression body =
            System.Linq.Expressions.Expression.Add(paramA, paramB);

        // Compile it
        System.Func<T, T, T> add =
            System.Linq.Expressions.Expression.Lambda<System.Func<T, T, T>>
            (body, paramA, paramB).Compile();

        return add;
    }


    private static System.Func<T, T, T> CompileSubtract<T>()
    {
        // Declare the parameters
        System.Linq.Expressions.ParameterExpression paramA =
            System.Linq.Expressions.Expression.Parameter(typeof(T), "a");

        System.Linq.Expressions.ParameterExpression paramB =
            System.Linq.Expressions.Expression.Parameter(typeof(T), "b");

        // Subtract the parameters
        System.Linq.Expressions.BinaryExpression body =
            System.Linq.Expressions.Expression.Subtract(paramA, paramB);

        // Compile it
        System.Func<T, T, T> subtract =
            System.Linq.Expressions.Expression.Lambda<System.Func<T, T, T>>
            (body, paramA, paramB).Compile();

        return subtract;
    }

    public static T MinValue = GetMinValue<T>();
    public static T MaxValue = GetMaxValue<T>();
    public static System.Func<T, T, T> Add = CompileAdd<T>();
    public static System.Func<T, T, T> Subtract = CompileSubtract<T>();
}



public static TSigned MapUnsignedToSigned<TUnsigned, TSigned>(TUnsigned ulongValue)
{
    TSigned signed = default(TSigned);
    unchecked
    {
        signed = Number<TSigned>.Add((TSigned)(dynamic)ulongValue, Number<TSigned>.MinValue);
    }

    return signed;
}


public static TUnsigned MapSignedToUnsigned<TSigned, TUnsigned>(TSigned longValue)
{
    TUnsigned unsigned = default(TUnsigned);
    unchecked
    {
        unsigned = (TUnsigned)(dynamic) Number<TSigned>
            .Subtract(longValue, Number<TSigned>.MinValue);
    }

    return unsigned;
}

相当于:

// return MapUnsignedToSigned<ulong, long>(ulongValue);
private static long MapULongToLong(ulong ulongValue)
{
    return unchecked((long)ulongValue + long.MinValue);
}


// return MapSignedToUnsigned<long, ulong>(longValue);
private static ulong MapLongToUlong(long longValue)
{
    return unchecked((ulong)(longValue - long.MinValue));
}