Вот пример с комментариями:
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
}
}
так что ты думаешь об этом?
c#
.net
floating-point
Александр Ефимов
источник
источник
c.d.Equals(d.d)
оцениваетtrue
как делаетc.f.Equals(d.f)
Ответы:
Ошибка в следующих двух строках
System.ValueType
: (Я вошел в справочный источник)(Оба метода есть
[MethodImpl(MethodImplOptions.InternalCall)]
)Когда все поля имеют ширину 8 байт, по
CanCompareBits
ошибке возвращает true, что приводит к побитовому сравнению двух разных, но семантически идентичных значений.Если хотя бы одно поле не имеет ширины 8 байт,
CanCompareBits
возвращает значение false, и код переходит к использованию отражения для циклического обхода полей и вызоваEquals
для каждого значения, которое правильно обрабатывается-0.0
как равное0.0
.Вот источник
CanCompareBits
из SSCLI:источник
IsNotTightlyPacked
.The bug also happens with floats, but only happens if the fields in the struct add up to a multiple of 8 bytes.
Я нашел ответ на http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx .
Основной частью является исходный комментарий
CanCompareBits
, которыйValueType.Equals
используется для определения, следует ли использоватьmemcmp
сравнение в стиле:Автор далее констатирует точно проблему, описанную ОП:
источник
Equals(Object)
дляdouble
,float
иDecimal
изменились в течение ранних проектов .net; Я думаю , что это более важно иметь виртуальноеX.Equals((Object)Y)
только возвращение ,true
когдаX
иY
неразличимы, чем иметь этот метод соответствует поведению других перегрузок (особенно с учетом того, что из - за неявный тип принуждения, перегруженныеEquals
методы не даже определить отношение эквивалентности !, например,1.0f.Equals(1.0)
возвращает false, но1.0.Equals(1.0f)
возвращает true!) Реальная проблема ИМХО не в том, как сравниваются структуры ...Equals
чтобы означать нечто иное, чем эквивалентность. Предположим, например, что кто-то хочет написать метод, который принимает неизменный объект и, если он еще не был кэширован, выполняетToString
его и кэширует результат; если он был кеширован, просто верните кешированную строку. Это не слишком разумная вещь, но это может плохо сработать,Decimal
поскольку два значения могут сравниваться одинаково, но приводить к разным строкам.Гипотеза Вилкса верна. То, что делает «CanCompareBits», проверяет, не является ли рассматриваемый тип значения «плотно упакованным» в памяти. Плотно упакованная структура сравнивается простым сравнением двоичных битов, составляющих структуру; слабо упакованная структура сравнивается путем вызова Equals для всех членов.
Это объясняет наблюдение SLaks, что оно воспроизводится со структурами, которые являются двойными; такие структуры всегда плотно упакованы.
К сожалению, как мы видели здесь, это вводит семантическое различие, потому что побитовое сравнение двойных чисел и сравнение равных двойных дает разные результаты.
источник
Половина ответа:
Отражатель говорит нам, что
ValueType.Equals()
делает что-то вроде этого:К сожалению, оба
CanCompareBits()
иFastEquals()
(оба статических метода) являются extern ([MethodImpl(MethodImplOptions.InternalCall)]
) и не имеют доступного источника.Вернемся к предположению, почему один случай можно сравнивать по битам, а другой - нет (возможно, проблемы с выравниванием?)
источник
Это действительно так для меня, с Mono gmcs 2.4.2.3.
источник
Более простой контрольный пример:
РЕДАКТИРОВАТЬ : ошибка также происходит с плавающей запятой, но происходит только в том случае, если поля в структуре добавить кратно 8 байтов.
источник
double
является0
. Ты не прав.Это должно быть связано с побитовым сравнением, поскольку
0.0
должно отличаться-0.0
только сигнальным битом.источник
Всегда переопределяйте Equals и GetHashCode для типов значений. Это будет быстро и правильно.
источник
Просто обновление для этой 10-летней ошибки: она была исправлена ( Отказ от ответственности : я автор этого PR) в .NET Core, которая, вероятно, будет выпущена в .NET Core 2.1.0.
В блоге объясняется ошибка и как я ее исправил.
источник
Если вы сделаете D2, как это
это так.
если вы сделаете это так
Это все еще ложь.
я т кажется, что это неверно , если структура содержит только двойник.
источник
Это должно быть связано с нулем, так как изменение строки
чтобы:
результаты сравнения являются правдой ...
источник