3

I want to convert a multidimensional array of int[,] to an array of ushort[,] and if possible without looping over each dimension.

I found a post where object[,] is converted to double[,] by using Array.Copy. Unfortunately this only works because the objects are allready of type double. Is there any chance to achieve a similar result for converting int to ushort (assuming it always fits)?

var input = new [,]
{
    {1,1,1},
    {2,2,2},
    {3,3,3}
};
var output = new ushort[3, 3];

// Convert
Array.Copy(input, output, input.Length);

The above code compiles, but fails on execution because it can not convert from int to ushort. I know why this happens, I just want to tell .NET that it should just convert. As I said I know the easiest solution are two loops. I am just curious if there is an alternative.

Conclusion: Unfortunately there is no fast and built-in way to do this. Therefore I recommend the obvious and readable solution of going for the double loop unless you really need lightning speed fast conversion.

for(var i=0; i<input.GetLength(0); i++)
for(var j=0; j<input.GetLength(1); j++)
    output[i,j] = (ushort)input[i,j];

However this is not the accepted solution since for my personal interest I was asking for the fastest alternative and that is, as expected, and evil and unsafe pointer conversion.

12
  • 1
    Are you certain that your int values wont exceed the threshold of a ushort? Commented Apr 13, 2016 at 15:54
  • Yes, based on the input of the method that creates int[,] I can be sure. Commented Apr 13, 2016 at 15:56
  • 1
    @EvilTak Linq cannot be used to create 2-D arrays. At best you can iterate over all the source items and flatten it to a 1-D array. Commented Apr 13, 2016 at 16:11
  • 1
    No, it is not a duplicate as it refers to an array of reference types. Commented Apr 13, 2016 at 16:13
  • 1
    True, but you can't "cast" an int to a ushort. The "cast" operator in C# actually does a conversion in that case, which Array.Copy` does not support. Commented Apr 13, 2016 at 16:22

3 Answers 3

3

This will work without any conversion overhead. Also, it's evil, but I'd do it.

    static unsafe void FastCopy(int[,] source, ushort[,] destination)
    {
        //pin the source and destination arrays
        fixed (int* pSource = source) fixed (ushort* pDestination = destination)
        {
            //compute the pointers for the elements just past the arrays
            //as termination conditions
            var pSourceEnd = pSource + source.Length;
            var pDestinationEnd = pDestination + destination.Length;

            //setup our iterator variables
            var pSourceCurrent = pSource;
            var pDestinationCurrent = pDestination;

            //loop over each element until you run out of elements in 
            //either the source or destination
            while (pSourceCurrent < pSourceEnd && pDestinationCurrent < pDestinationEnd)
            {
                //copy the two lowest bytes in each source int to the
                //destination ushort
                *pDestinationCurrent++ = *(ushort*)pSourceCurrent++;
            }
        }
    }
Sign up to request clarification or add additional context in comments.

15 Comments

@InBetween: It loops but it doesn't loop over both dimensions, thus meeting his requirement.
Yes it still iterates but not in a double loop (I know it has the same number of iterations). Like I said, I was curious if there was something faster than the obvious solution. I agree with you that it should only be used when everything else is too slow.
It eliminates the overhead of the second loop. It also eliminates the per-element array bounds check. Finally, no conversion overhead as you're not casting a value - just a pointer. The only way this can be made faster AFAIK is to copy from int to int, in which case CIL's cpblk instruction or x86's rep movsb would be faster and need no loop.
@Toxantron & hoodaticus Very true I misread the requirements. I'd still avoid this whenever possible ;) but it does the job fast. +1
Like I said, I know it has the same number of iterations but it loses the overhead of an additional nested loop.
|
1

Well, you can't convert in-place because arrays are low-level structures, so the memory footprint of an int[,] and a ushort[,] would be different. Also, there's not a built-in method to convert a 2-D array, so the simplest option is to loop and fill a new array with the converted values.

5 Comments

I was hoping there was some neat overload of ConvertAll somewhere that uses fancy unsafe code and pointers to do this.
@Toxantron: such code would hardly be more efficient than simple managed loops, since converting an int to a ushort can't just be done by aliasing stuff through pointers, no matter how fancy you get.
I think for a positive integer the two smallest bytes of int should be castable to ushort. Am I wrong?
For a single value, yes. But an array is a set of contiguous values in memory. So the resulting array would be a smaller size. So it's a conversion, not a cast.
You are right, the simplest solution is a loop, but my posts starts by saying that I am aware of this. Anyway thanks for the clarification for future readers.
1

You seem to be conflating very different types of "casts". Do not mix reference conversions with type conversions and boxing conversions:

 class Foo: IFoo { ... }
 var foo = new Foo();
 var iFoo = foo; //implicit reference conversion

 var i = 1;
 var o = (object)i; //boxing
 var u = (uint)i; //type conversion

Reference conversions do not change the object at all, they only change the type of the reference pointing at it. Type conversions (for lack of a better term, english is not my native language) give you back an alltogether different object. Boxing and unboxing conversions are special but behavior wise, in the scope of your question, are similar to reference conversions.

That C# has the same syntax for all these semantically very different casts is, in my opnion, unfortunate.

Array.Copy will allow you to copy between arrays of different type when there is a reference type conversion or boxing/unboxing coversion between them. The reason being that you are not really changing the object, just it's reference (the bits that make up the object stay the same and therefore its size too (again, not strictly true with boxing/unboxing)). That is, the following are all valid:

var foos = new Foo[] { null, null, null };
var iFoos = new IFoo[3];
Array.Copy(foos, iFoos, 3); //reference conversion

var ints = new[] { 1, 2, 3 };
var objs = new object[3];
Array.Copy(ints, objs, 3); //boxing conversion
Array.Copy(objs, ints, 3); //unboxing conversion

While the following is not:

var ints = new[] { 1, 2, 3 };
var uints = new uint[3];
Array.Copy(ints, uints, 3); //type conversion. Runtime error.

Worth noting that the following is also not valid:

var objs = new object[] { 1, 2, 3 }; //boxed int[]
var uints = new uint[3];
Array.Copy(objs, uints, 3); // Unboxing conversion. Runtime error. Huh?

Why is that? Isn't there an unboxing conversion from object to uint? Yes there is, but the boxed value is really an int and you can only unbox a boxed value to its real type. Due to the same reason, the following will also give you a runtime error:

obect i = 1;
var u = (uint)i;

So what does that have to do with anything you've asked?

Well, that there is no inbuilt way to convert a n-dimensional array from one type array to another if the conversion is not a simple reference or boxing/unboxing conversion. You either have to flatten the array using Linq or create your own extension method and in either case you will have to iterate the array to perform the conversion because you have to create a new object (potentially with different size, bits, etc.) for each element in the source array.

Obviosuly, performance will be significantly slower than Array.Copy.

3 Comments

Great explanation, but I think your example var objs = new[] { 1, 2, 3 } needs to be var objs = new object[] { 1, 2, 3 }, otherwise an int[] type will be inferred.
@DStanley whoops, true. Thanks!
Thanks for a great explanation. Not what I was looking for but still +1 for future readers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.