2013-07-23 5 views
1

저는 MiscUtils 라이브러리 (Marc G.와 Jon S.에게 감사합니다)를 사용 중이며 일반 Sqrt 함수를 추가하려고합니다.일반 Sqrt 구현

class N<T> 
{ 
    public N(T value) 
    { 
     Value = value; 
    } 

    public readonly T Value; 

    public static implicit operator T(N<T> n) 
    { 
     return n.Value; 
    } 

    public static implicit operator N<T>(T value) 
    { 
     return new N<T>(value); 
    } 

    public static T operator /(N<T> lhs, T rhs) 
    { 
     // Operator.Divide is essentially a wrapper around 
     // System.Linq.Expressions.Expression.Divide 
     return Operator.Divide(lhs.Value, rhs); 
    } 
} 

// fails with: No coercion operator is defined 
// between types 'System.Double' and 'N`1[System.Single]'. 
var n = new Numeric<float>(1f); 
var x = Operator.DivideAlternative(n, 1.0); 

// this works as the N<T> is first converted to a 
// float via the implicit conversion operator 
var result = n/1.0; 

지금, 나는 이유 이런 일이 실현,하지만 난 아직 주위에 방법을 생각 할 수 없었던 : 문제는 쉽게 재현 할 수 있습니다. 참고로, 현재는 Sqrt 구현입니다. 표현 나무를 만드는 경험이 거의 없습니다.

public static double Sqrt<T>(T value) 
{ 
    double oldGuess = -1; 
    double guess = 1; 
    while(Abs(guess - oldGuess) > 1) 
    { 
     oldGuess = guess; 
     // the first evaluated call to DivideAlternative throws 
     guess = Operator.Divide(
        Operator.AddAlternative(guess, 
         Operator.DivideAlternative(value, guess)), 
        2); 
    } 

    return guess; 
} 

편집 : 좋아, 그래서 난 내 자신에이 문제를 해결,하지만 난 분명히 너무 멀리 갔다 가능한 한 간단하게 질문을 유지하기위한 시도와 너무 많은 시간을 도우려고 혼란 사람들의 질문에 대답을 보냈다 .

그래서 전체적으로이 문제가 있습니다.

나는 두 개의 클래스; 하나는 변환을 수행하고 다른 하나는 이미지 데이터 (픽셀)의 통계적 분석을 수행합니다. 문제가 동일하므로 후자에 초점을 맞추어 보겠습니다.

abstract class ImageStatistics 
{ 
    private readonly object _pixels; 

    public ImageStatistics(object pixelArray) 
    { 
     Pixels = pixelArray; 
    } 

    // calculate the standard deviation of pixel values 
    public double CalcStdDev(); 
} 

픽셀 배열은 숫자 유형이 될 수 있습니다. 실제로는 float, int, ushort 또는 byte이됩니다. 지금 제네릭은 다음과 같은 일을 할 수 없기 때문에 :

public T Add<T>(T lhs, T rhs) 
{ 
    return lhs + rhs; // oops, no operator + for T 
} 

적절한 배열 유형으로 변환하지 않고 픽셀 값 자체에 대한 통계 분석을 수행 할 수 없습니다. 따라서 N 픽셀 유형을 지원하려면 ImageProcessor의 N 하위 클래스가 있어야합니다.

글쎄, 그건 짜증. 나는 픽셀 데이터가 T[] 인 일반적인 ImageProcessor<T> 클래스를 갖고 싶다. 그래서, 나는 이것을 허용 할 MiscUtils 라이브러리를 들여다 보았다.

+4

Math.Sqrt()를 약 5 나노초합니다. 이 코드는 ...하지 않습니다. –

+1

@IgbyLargeman 제 1 자와 제 2 자 (포스터 및 스택 교환)가 누구인지 확실하지 않지만이 질문은 예를 들어 주목할만한 "제 3 자 라이브러리"인 .NET Framework와 마찬가지로 적법합니다. .. – AakashM

+0

@HansPassant : 현재 성능에 지나치게 신경 쓰지는 않습니다. 단지 작동 시키려합니다. 나는 나중에 그것에 대해 걱정할 수 있습니다. 현재 나의 유스 케이스는 다른 많은 통계를 집계 한 후에 이것을 한 번 호출하는 것이다. 이것에 대한 한 가지 요청은 데이터를 준비하는 데 걸리는 시간과 거의 관계가 없습니다. 나는 이것을 일반 대중에게 공개 할 계획이 없다. –

답변

-1

이 작동하지만, 조금 추한 :

public static implicit operator Numeric<T>(double value) 
{ 
    return new Numeric<T>((T)Convert.ChangeType(value, typeof(T))); 
} 

public static implicit operator double(Numeric<T> n) 
{ 
    return Convert.ToDouble(n.Value);   
} 

그것은이 훨씬 덜 일반적인하게 지원되는 각 유형에 대해 반복해야합니다. 나는 거기에 좋은 측정을 위해 IConvertible 제약을 때렸다. 누구든지 더 나은 해결책을 가지고 있다면 나는 모든 귀입니다.

+1

Downvoter, 설명해주십시오. 이것은 원래의 문제를 실제로 해결하는 유일한 대답입니다. –

0

콘솔 응용 프로그램 객체의 배열 (알 수없는 유형)를 작성하고

using System; 

namespace GenericSqrt 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var array = new object[] { "2", null, 4.1f, 4.444D, "11.3", 0, "Text", new DateTime(1, 1, 1) }; 

      foreach (var value in array) 
      { 
       try 
       { 
        Console.WriteLine(Sqrt(value)); 
       } 
       catch (Exception ex) 
       { 
        Console.WriteLine(ex.Message); 
       } 
      } 
      Console.ReadLine(); 
     } 

     private static double Sqrt(object value) 
     { 
      double converterValue = Convert.ToDouble(value); 
      return Math.Sqrt(converterValue); 
     } 
    } 
} 

출력은 다음과 같습니다 (더블) 광장 경로를 계산 : 같은 종류의 숫자 유형 중 하나가 참이면

1.4142135623731 
0 
2.02484564958235 
2.10807969488822 
3.36154726279432 
0 
Input string was not in a correct format. 
Invalid cast from 'DateTime' to 'Double'. 

당신이 말했듯이, 해결할 문제는 없습니다.

+0

그것은 복싱의 가치이며, 한 시간 전에 더블로 변환하는 것과 같은 똑같은 일을했습니다. 고마워요.하지만 이미 생각했습니다. 그래도 한 가지는 맞습니다. 나는 이것을 필요 이상으로 복잡하게 만들고있었습니다. –

+0

개체를 특정 형식으로 변환하는 코드는 어디에 있습니까? 제 생각에는 당신이 실제로 제네릭을 사용하지 않으려 고하는 문제를 만들고 있다고 생각합니다. 출력 유형은 항상 constant - double입니다. – Nenad

+0

대답이 아니라 질문입니다. 나는 시나리오를 단순화하려고 실수를 저질렀고 너무 멀리 갔다고 생각한다. 내가 처음 질문을 표현한 방식을 고려할 때, 당신의 대답은 최고입니다. 지금 당장 질문에 대한 정보를 추가했습니다. 네 개의 클래스에서 코드를 복사하지 않으려면 여기에 제네릭 형식이 실제로 필요합니다. 내가하지 않는 * 필요 *는'N '타입입니다. 나는 필요가 없을 때 숫자 형을 감싸는 것에 집중했다. 나는 (지금,)'ImageProcessor'에서 MiscUtils 연산자를 사용하고 어리석은'N '을 무시해야합니다. –

0

이 코드를 유지 관리해야하는 방법을 고려할 때 아마도 노력할 가치가 없다고 말하면서 머리말을 붙이겠습니다. 나는 이것을 약 10 분 안에 썼다. 너무나 훌륭한 것은 기대하지 마라.

// You'll need this 
public interface ISquareRootHelper 
{ 
    double Sqrt<T>(T value) 
     where T : struct; 
} 

class Program 
{ 
    private static ISquareRootHelper helper; 

    // Build the helper 
    public static void BuildSqrtHelper() 
    { 
     // Let's use a guid for the assembly name, because guid! 
     var assemblyName = new AssemblyName(Guid.NewGuid().ToString()); 

     // Blah, blah, boiler-plate dynamicXXX stuff 
     var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); 
     var dynamicModule = dynamicAssembly.DefineDynamicModule(assemblyName.Name); 
     var dynamicType = dynamicModule.DefineType("SquareRootHelper"); 

     // Let's create our generic square root method in our dynamic type 
     var sqrtMethod = dynamicType.DefineMethod("Sqrt", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); 
     sqrtMethod.SetReturnType(typeof(double)); 

     // Well, I guess here is where we actually make the method generic 
     var genericParam = sqrtMethod.DefineGenericParameters(new[] {"T"}); 
     genericParam[0].SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint); 

     // Add a generic parameter, and set it to override our interface method 
     sqrtMethod.SetParameters(genericParam); 
     dynamicType.DefineMethodOverride(sqrtMethod, typeof(ISquareRootHelper).GetMethod("Sqrt")); 

     // Magic sauce! 
     var ilGenerator = sqrtMethod.GetILGenerator(); 

     // Math.Sqrt((double)value); 
     ilGenerator.Emit(OpCodes.Ldarg_1); // arg_0 is this* 
     ilGenerator.Emit(OpCodes.Conv_R8); 

     var mathSqrtMethodInfo = typeof(Math).GetMethod("Sqrt"); 
     ilGenerator.EmitCall(OpCodes.Call, mathSqrtMethodInfo, null); 

     ilGenerator.Emit(OpCodes.Ret); 

     // Since we're overriding the interface method, we need to have the type 
     // implement the interface 
     dynamicType.AddInterfaceImplementation(typeof(ISquareRootHelper)); 

     // Create an instance of the class 
     var sqrtHelperType = dynamicType.CreateType(); 
     helper = (ISquareRootHelper)Activator.CreateInstance(sqrtHelperType); 
    } 

    public static void Main(string[] args) 
    { 
     BuildSqrtHelper(); 

     Console.WriteLine(helper.Sqrt((short)64)); // Works! 
     Console.WriteLine(helper.Sqrt((ushort)64)); // Works! 
     Console.WriteLine(helper.Sqrt((int)64));  // Works! 
     Console.WriteLine(helper.Sqrt((uint)64));  // Works*! 
     Console.WriteLine(helper.Sqrt((byte)64));  // Works! 
     Console.WriteLine(helper.Sqrt((sbyte)64)); // Works! 
     Console.WriteLine(helper.Sqrt((float)64)); // Works! 
     Console.WriteLine(helper.Sqrt((double)64)); // Works! 
     Console.WriteLine(helper.Sqrt((long)64));  // Works! 
     Console.WriteLine(helper.Sqrt((ulong)64)); // Works*! 

     // Let's try non-primitives! 
     Console.WriteLine(helper.Sqrt(DateTime.Now)); // Doesn't fail, but doesn't actually work 
     Console.WriteLine(helper.Sqrt(Guid.NewGuid())); // InvalidProgramException! 
    } 
} 

어쨌든, 나는 이것이 가능하다는 것을 증명합니다. 당신이 그것을 사용할 때 원시 타입만을 전달해야한다. 그렇지 않으면 모든 실패가 풀릴 것이다.실제로 스택을 불균형하게하기 때문에 8 바이트보다 큰 구조체를 전달할 때만 예외가 발생합니다. 메서드에서 sizeof(T) 같은 검사를 수행 할 수 없습니다. JITing 프로세스 중에 오류가 발생하기 때문입니다.

또한 일부 유형 옆에는 *이 있습니다. 서명되지 않은 숫자와 부호가있는 숫자를 전달할 때 컴파일러에서 수행하는 약간의 추가 논리 및/또는 Math.Sqrt이 있으며 이는 음수와 관련이 있습니다. 예를 들면 다음과 같습니다.

Console.WriteLine(Math.Sqrt(unchecked((uint)-2))); // 65535.9999847412 
Console.WriteLine(helper.Sqrt(unchecked((uint)-2))); // NaN :(

위의 내용을 향상시키고이를 포착 할 수 있습니다. 또한 특히 IL에 익숙하지 않은 경우이 솔루션을 사용하지 않는 것이 좋습니다. 또한, 원하는 작업을 처리하기위한 여러 가지 방법을 작성하는 것보다 더 복잡하고 복잡합니다.

+0

예 ... Sqrt 함수 (add, mul, div, sub 등)는 수백만 번이나 수백만 번 수행됩니다. 반사는 아마도 나쁜 생각 일 수 있습니다. 4 가지 수업 방식을 고수 할 것입니다. 좀 더 간단한 방법으로 할 수 있습니다. 답변을 잠깐 게시했습니다. 앞뒤로 간 후에 나는 어리석은 래퍼 유형을 포기하고 ImageProcessor 자체에서 MiscUtil의 연산자를 사용하는 것이 나을 것이라고 생각합니다. 나는 연산자의 구문을 잃어 버리지 만, 일을 간단하고 유연하게 유지한다. –

+0

글쎄, 난 여전히 위의 묵과 아니에요,하지만 리플렉션은 한 번만 발생합니다. 기본적으로 .NET 런타임에서는 수행하려는 작업이 가능하지만 C#에서는 수행 할 수 없습니다. 나는 당신이 원하는 것을 얻기 위해 필요한 클래스와 코드를 런타임에 생성 할 것이다. 한 번 타입과 메쏘드를 만들었고, 클래스로 코드를 컴파일 한 것처럼 그것을 호출하기 때문에 C#으로 작성할 수있는 것처럼 거의 빠릅니다. 역동적이고 인라인 될 수 없기 때문에 사소한 성능 손실이 있지만, 마이크로 초 정도의 가능성이 있습니다. –

1

Math.Sqrt은 두 개가 필요하므로 왜 그걸 제공하지 않으시겠습니까?

public static double Sqrt<T>(T value) 
{ 
    return Math.Sqrt(Convert.ToDouble(value)); 
} 

캐스팅은 dynamic 일 수도 있습니다.

public static double Sqrt<T>(T value) 
{ 
    return Math.Sqrt((dynamic) value); 
} 

이 기술

은 또한 같은 연산자를 사용할 수 있습니다 :

public static T Add<T>(T a, T b) 
{ 
    return (dynamic) a + (dynamic) b; 
}