Ошибка с плавающей точкой c (дробная часть полностью потеряна) - PullRequest
0 голосов
/ 10 июля 2020
quotient = 43156414f / 3;

Я получил quotient == 1438547 2 (совершенно не так, как должно быть истинное значение: 1438547 1,333 ... ). Он полностью потерял всю дробную часть!

Я знаю о неточности вычислений с плавающей запятой (кажется, не все, что мне следовало бы знать), но, как я сообщил, ошибка могла появиться при гораздо более дальних значащих цифрах. Но дивиденд здесь всего лишь 8-значное число. Почему возникает такая драматическая c ошибка?

Необязательный подвопрос : Какие правила следует учитывать, чтобы предвидеть такие ошибки в будущем?

Просто на заметку : изменение типа делителя с float на double решает эту проблему.

Ответы [ 2 ]

7 голосов
/ 10 июля 2020

float имеет точность 6-9 цифр. Ваше значение слишком велико, чтобы поместиться в число с плавающей запятой без потерь.

double имеет точность примерно 15-17 цифр.

В качестве иллюстрации проверьте значение (int)43156414f или (double)43156414f - они оба 4315641 6

3 голосов
/ 10 июля 2020

Ну, float (Single) использует 23 бит для мантиссы

https://en.wikipedia.org/wiki/Single-precision_floating-point_format

Итак, float может представляют целые числа до 2**24 - 1 == 16777215, что от близко от до 14385471. Возможности, которые у нас есть:

 01001011 01011011 10000001 00111111 correponds to 14385471f (let's add 1 bit)
 01001011 01011011 10000001 01000000 correponds to 14385472f

Итак, как мы видим, у нас есть только 14385471f и 14385472f на выбор; нет места для 14385471.33333f в случае floate. Давайте посмотрим

 float x = 14385471.3333333333f;

 byte[] data = BitConverter.GetBytes(x);

 Console.Write(" ", string.Join(" ", data
   .Reverse()
   .Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))));

У нас будет

 01001011 01011011 10000001 00111111

, что соответствует 14385471f. Еще больше проблем у нас с

 quotient = 43156414f / 3;

Теперь 43156414f > 2**24 - 1 (16777215) и 43156414f не могут быть представлены как float, а

 01001100 00100100 10100000 11110000 corresponds to 43156416 

Таким образом, фактическое значение quotient is

 43156416 / 3 == 14385472      

Наконец, если вы хотите иметь 14385471.33, Single недостаточно, попробуйте double:

 // 14385471.333333334
 double quotient = 43156414d / 3;
...