programing

이 이상한 행동을 C#으로 서명된 플로트로 설명할 수 있는 사람이 있습니까?

iphone6s 2023. 5. 2. 22:27
반응형

이 이상한 행동을 C#으로 서명된 플로트로 설명할 수 있는 사람이 있습니까?

다음은 설명이 포함된 예제입니다.

class Program
{
    // first version of structure
    public struct D1
    {
        public double d;
        public int f;
    }

    // during some changes in code then we got D2 from D1
    // Field f type became double while it was int before
    public struct D2 
    {
        public double d;
        public double f;
    }

    static void Main(string[] args)
    {
        // Scenario with the first version
        D1 a = new D1();
        D1 b = new D1();
        a.f = b.f = 1;
        a.d = 0.0;
        b.d = -0.0;
        bool r1 = a.Equals(b); // gives true, all is ok

        // The same scenario with the new one
        D2 c = new D2();
        D2 d = new D2();
        c.f = d.f = 1;
        c.d = 0.0;
        d.d = -0.0;
        bool r2 = c.Equals(d); // false! this is not the expected result        
    }
}

여러분은 이것에 대해 어떻게 생각하나요?

버그는 다음 두 줄에 있습니다.System.ValueType(참조 소스에 발을 들여놓았습니다)

if (CanCompareBits(this)) 
    return FastEqualsCheck(thisObj, obj);

(두 가지 방법 모두)[MethodImpl(MethodImplOptions.InternalCall)])

모든 필드의 너비가 8바이트일 때,CanCompareBits실수로 true를 반환하여 서로 다르지만 의미적으로 동일한 두 값을 비트 단위로 비교합니다.

하나 이상의 필드가 8바이트가 아닌 경우CanCompareBitsfalse를 반환하고, 코드는 반사를 사용하여 필드를 루프하고 호출합니다.Equals각 값에 대해 정확하게 처리합니다.-0.0와 동등한0.0.

의 출처는 다음과 같습니다.CanCompareBitsSSCLI에서:

FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj != NULL);
    MethodTable* mt = obj->GetMethodTable();
    FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND

저는 http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx 에서 답을 찾았습니다.

핵심 내용은 다음에 대한 소스 의견입니다.CanCompareBits,어떤.ValueType.Equals사용 여부를 결정하는 데 사용memcmp- 스타일 비교:

CanCompareBits의 주석에는 "값 유형에 포인터가 포함되지 않고 꽉 채워진 경우 true를 반환합니다."라고 표시됩니다.FastEqualsCheck는 "memcmp"를 사용하여 비교 속도를 높입니다.

저자는 계속해서 OP가 설명한 문제를 정확하게 설명합니다.

여러분이 부유물만을 포함하는 구조를 가지고 있다고 상상해 보세요.하나는 +0.0을 포함하고 다른 하나는 -0.0을 포함하면 어떻게 됩니까?동일해야 하지만 기본 이진 표현은 다릅니다.동등한 방법을 재정의하는 다른 구조를 중첩하면 해당 최적화도 실패합니다.

Vilx의 추측은 맞습니다."CanCompareBits"는 해당 값 유형이 메모리에서 "엄격하게 패킹"되었는지 확인합니다.꽉 채워진 구조는 구조를 구성하는 이진 비트를 단순히 비교하여 비교합니다. 느슨하게 채워진 구조는 모든 구성원에 대해 Equals를 호출하여 비교합니다.

이것은 SLak의 관찰을 설명합니다. 모두 이중 구조로 재처리합니다. 이러한 구조는 항상 꽉 차 있습니다.

불행히도 여기서 본 것처럼, 이것은 의미론적 차이를 초래합니다. 왜냐하면 이중의 비트별 비교와 동등한 중복의 비교는 서로 다른 결과를 제공하기 때문입니다.

절반의 대답:

리플렉터는 다음과 같이 말합니다.ValueType.Equals()다음과 같은 작업을 수행합니다.

if (CanCompareBits(this))
    return FastEqualsCheck(this, obj);
else
    // Use reflection to step through each member and call .Equals() on each one.

도 둘 다.CanCompareBits()그리고.FastEquals() 다 방법)은 외부으)로 표시)입니다.[MethodImpl(MethodImplOptions.InternalCall)]및 가능한 및 사용 가능한 소스가 없습니다.

다시 한 사례는 비트로 비교할 수 있고 다른 사례는 비트로 비교할 수 없는 이유를 추측합니다(정렬 문제일 수 있음).

Mono의 gmcs 2.4.2.3과 함께라면 저도 마찬가지입니다.

간단한 테스트 사례:

Console.WriteLine("Good: " + new Good().Equals(new Good { d = -.0 }));
Console.WriteLine("Bad: " + new Bad().Equals(new Bad { d = -.0 }));

public struct Good {
    public double d;
    public int f;
}

public struct Bad {
    public double d;
}

편집: 이 버그는 플로트에서도 발생하지만 구조체의 필드가 8바이트의 배수가 될 경우에만 발생합니다.

그것은 조금씩 비교하는 것과 관련이 있을 것입니다, 왜냐하면.0.0는 달야합니다와 합니다.-0.0신호 비트에 의해서만.

…이것에 대해 어떻게 생각하십니까?

값 유형에 대해서는 항상 Equals 및 GetHashCode를 재정의합니다.그것은 빠르고 정확할 것입니다.

이 10년 된 버그에 대한 업데이트일 뿐입니다: 수정되었습니다(Disclaimer:의 이 PR)의 저자입니다.NET Core는 아마도 에 출시될 것입니다.NET Core 2.1.0.

블로그 게시물에서 버그와 수정 방법에 대해 설명했습니다.

만약 당신이 D2를 이렇게.

public struct D2
{
    public double d;
    public double f;
    public string s;
}

사실입니다.

이렇게 만들면,

public struct D2
{
    public double d;
    public double f;
    public double u;
}

여전히 거짓입니다.

만약 그 구조물이 더블만 유지한다면 그것은 거짓인 것처럼 보입니다.

라인을 변경한 후에는 관련이 0이어야 합니다.

d.d = -0.0

대상:

d.d = 0.0

결과적으로 그 비교는 사실입니다...

언급URL : https://stackoverflow.com/questions/2508945/can-anyone-explain-this-strange-behavior-with-signed-floats-in-c

반응형