Mapping a ulong to a long in C#?
Asked Answered
P

3

8

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);
}
Projector answered 1/12, 2016 at 0:20 Comment(2)
Hey there! You might want to consider asking this question on codereview.stackexchange.com instead of here. Peer-reviewing code to improve performance is one of the things they're great at.Brachyuran
you can check https://mcmap.net/q/841867/-fast-integer-abs-function to avoid the branch mispredictionGroping
M
15

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.

Mckeon answered 1/12, 2016 at 4:57 Comment(8)
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.Hellen
IMHO, you can replace + long.MinValue and - long.MinValue with ^ long.MinValue.Depressed
@Hellen 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?Mckeon
@TannerSwett Yes. The solution with overlapping fields does the conversion with zero instructions.Hellen
@Hellen But you left out the part where long.MinValue is added or subtracted (or xored) in order to preserve ordering.Mckeon
@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.Hellen
Your sample incorrectly map this value: 18446651727900729360. Result in hex should be FFFFAC0310DE6010. Your sample returns: 7FFFAC0310DE6010. The code by @Hellen below produces correct result.Longwinded
@AlexDragokas No, 0x7FFFAC0310DE6010 is the correct return value for what the asker here is asking for.Mckeon
H
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.

Hellen answered 1/12, 2016 at 5:9 Comment(14)
But your code does not map values as OP want. Also, AFAIK, overlapped fields required that your code was loaded with full trust privileges.Depressed
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.Mckeon
Agree with @TannerSwett, unchecked conversion long to ulong is essentially no-op in MSIL.Depressed
@TannerSwett You are correct. unchecked conversion of long to ulong is a no-op. But the assignment is not a no-op.Hellen
@PetSerAl I agree, the content of the method is a no-op. It is the method call itself that takes time (specifically the assignment).Hellen
@Hellen 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.Depressed
@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.Hellen
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?Depressed
@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.Hellen
@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 :-)Waken
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.Cardoso
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.Cardoso
@DamnVegetables after 4 years...finally I am vindicated....Hellen
Thanks, @Aron. Your code is the only one that correctly make a convertion among all codes in this thread.Longwinded
A
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));
}
Addieaddiego answered 16/3, 2018 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.