7

I am trying to map a ulong to a long (and vice-versa), and a uint to a int (and vice-versa), as shown below - in order to save the values in a MS-SQL-database with signed types integer and biginteger only.

I do this because I have to check (in the database) whether a number (uint, ulong) is within which range in a bunch of uint/ulong ranges (IPs - v4 & v6; actually the ulong is in reality a uint128 composed of two ulongs).

UlongToLong

UIntToInt

Is there a more efficient way to accomplish this than the code I have here:

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);
}
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Hey there! You might want to consider asking this question on http://codereview.stackexchange.com/ instead of here. Peer-reviewing code to improve performance is one of the things they're great at. – Will Ray Dec 01 '16 at 00:25
  • you can check http://stackoverflow.com/a/21854586/1383168 to avoid the branch misprediction – Slai Dec 01 '16 at 00:49

3 Answers3

14

Option 1: order-preserving map

It sounds like you're asking for a map which preserves order, meaning that, for example, if x and y are ulongs and x < y, then MapUlongToLong(x) < MapUlongToLong(y).

Here's how to do that:

To map from ulong to long, cast and add long.MinValue. To map from long back to ulong, subtract long.MinValue and cast. In either case, use an unchecked context so that overflow conditions are ignored.

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

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

The logic for uint and int is exactly analogous.

(Option 1 is the original answer I wrote in 2016. I added option 2, and the comparison of the two, in 2021.)

Option 2: non-order-preserving map

I don't think this is what you're asking for, but it's even easier to do the conversion if you don't care about preserving order.

These functions work the same way as the above functions, except that we don't bother to add or subtract long.MinValue.

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

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

Which option is better?

Option 1 preserves order and option 2 doesn't, so if you need to preserve order, use option 1.

How long do the functions in option 1 take to execute? Well, those functions will probably be inlined and optimized by the JIT compiler, and they're ultimately asking the CPU to do something very, very simple. I'm guessing that each function call will take less than 1 nanosecond.

One of the comments describes this less-than-a-nanosecond execution time as being "relatively slow." If a nanosecond is too slow for you, you may want to use option 2.

The functions in option 2 will also probably be inlined and optimized by the JIT compiler, and it turns out that as far as the CPU is concerned, those functions do literally nothing. Therefore, no machine code will be generated for those functions, and so each function call will take no time at all—in other words, 0 nanoseconds.

Aron's answer does the same thing as option 2, and I'm guessing that it will run equally fast, too.

Tanner Swett
  • 3,241
  • 1
  • 26
  • 32
  • Although this is correct. It is "relatively" slow as you still need to do a copy of a long memory field...I would prefer a blitted approach for awesome. – Aron Dec 01 '16 at 05:10
  • IMHO, you can replace `+ long.MinValue` and `- long.MinValue` with `^ long.MinValue`. – user4003407 Dec 01 '16 at 05:25
  • @Aron Both of my methods compile down to just four instructions: load argument, load immediate, add or subtract, return. If one of these method calls is inlined, that becomes just two instructions in the best case. Do you have a solution that uses fewer than two instructions? – Tanner Swett Dec 01 '16 at 05:42
  • @TannerSwett Yes. The solution with overlapping fields does the conversion with zero instructions. – Aron Dec 01 '16 at 05:58
  • @Aron But you left out the part where `long.MinValue` is added or subtracted (or xored) in order to preserve ordering. – Tanner Swett Dec 01 '16 at 06:39
  • @TannerSwett That is true. But the context of the question is that the OP wants a unique mapping for storing and restoring a `ulong` to a MySQL database. I didn't read a specific mapping was required. A migration to the new much more natural IEEE conversion is a simple step too. – Aron Dec 01 '16 at 07:05
  • Your sample incorrectly map this value: 18446651727900729360. Result in hex should be FFFFAC0310DE6010. Your sample returns: 7FFFAC0310DE6010. The code by @Aron below produces correct result. – Alex Dragokas Nov 03 '21 at 18:55
  • @AlexDragokas No, 0x7FFFAC0310DE6010 is the correct return value for what the asker here is asking for. – Tanner Swett Nov 03 '21 at 19:47
1

Althought Tanner Swett is correct. A much nicer and dirty solution is to tell .net to map access for a ulong to the same memory address as a long. This will give you instantaneous conversion speed.

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; }
    }
}

By setting your entity framework POCO to use the attributes shown, you can control the memory addresses that the fields are mapped to.

Therefore, no conversion ever occurs.

This code is 100% faster than Tanner Swett's.

Aron
  • 15,464
  • 3
  • 31
  • 64
  • 2
    But your code does not map values as OP want. Also, AFAIK, overlapped fields required that your code was loaded with full trust privileges. – user4003407 Dec 01 '16 at 05:19
  • 2
    Can you provide some sort of source for the fact that my code will spend time converting? Since the CPU doesn't store signed and unsigned values differently, I would expect my `(long)` and `(ulong)` casts to turn into nothing at all once they've been compiled into machine code. – Tanner Swett Dec 01 '16 at 05:21
  • Agree with @TannerSwett, unchecked conversion `long` to `ulong` is essentially no-op in MSIL. – user4003407 Dec 01 '16 at 05:28
  • @TannerSwett You are correct. `unchecked` conversion of `long` to `ulong` is a no-op. But the assignment is not a no-op. – Aron Dec 01 '16 at 05:55
  • @PetSerAl I agree, the content of the method is a no-op. It is the method call itself that takes time (specifically the assignment). – Aron Dec 01 '16 at 06:00
  • @Aron Property accessor is also method. And with your code you need two method calls for conversion: one set accessor (with the assignment) and one get accessor. – user4003407 Dec 01 '16 at 07:56
  • @PetSerAl I am aware of that, but you can engineer the code to miss some steps if you do it right. You can write to the property directly, then save the POCO straight to EF. No conversion step (between ulong/long) ever takes place. – Aron Dec 01 '16 at 08:01
  • Even if you work with bare fields, I can not see any win here. If you already have `long` on evaluation stack, then unchecked cast to `ulong` take 0 (zero) instructions. With overlapped fields you need to store field and to load field, clear lose. If you have `long` field and want to load it on evaluation stack as `ulong`, then you just load `long` and do free cast to `ulong`. With overlapped fields you can just load `ulong` counterpart field, but still no win here. So, can you actually show how to *do it right* and post the code, which would be faster than @TannerSwett's? – user4003407 Dec 01 '16 at 08:55
  • @PetSerAl Yes, it is a clear lose, IFF you control all the code. CLEARLY the `long` code is outside of the control of the OP, which is why he is using `long`. The OP is using MySQL to read the `long` into the database. With a conversion, you need to do your Business logic in `ulong` then pass it to your adaptor tier logic, to convert the data, then pass that to the persistance layer. AN ADDITIONAL LAYER OF INDIRECTION, a clear loss. – Aron Dec 01 '16 at 09:26
  • 5
    @Aron: I laughed out loud when, in the middle of this discussion of how to shave nanoseconds off the execution time, you said "then save the POCO straight to EF". I'm sure it was a throwaway comment, since if EF really was in the mix, you'd have bigger fish to fry :-) – Gary McGill Dec 01 '16 at 09:42
  • It seems that people didn't like your idea, but in my case, I just wanted to to store a `ulong` in place of `long` so this seems to be a very efficient method. I was thinking about `BitConverter.ToInt64(BitConverter.GetBytes(val), 0)`, but this involves two conversions. – Damn Vegetables Oct 31 '20 at 09:38
  • Wrote a simple test code and tried to convert ulong -> long 10M times. My BitConverter code took 0.0609 seconds, your code took 0.0049 seconds. That's more than 10 times faster. – Damn Vegetables Oct 31 '20 at 09:46
  • @DamnVegetables after 4 years...finally I am vindicated.... – Aron Oct 31 '20 at 10:10
  • Thanks, @Aron. Your code is the only one that correctly make a convertion among all codes in this thread. – Alex Dragokas Nov 03 '21 at 19:08
1

Necromancing.
Generic answer based on the answer of 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;
}

equivalent:

// 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));
}
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442