2015-11-26 4 views
0

Я только что проверил с LINQPad:Не могли бы вы объяснить поведение метода потолка?

Не могли бы вы объяснить мне, почему/как метод потолка реагирует так? Обратите внимание на 123.12 в середине.

Math.Ceiling(123.121 * 100)/100 'display 123.13 
Math.Ceiling(123.1200000000001 * 100)/100 'display 123.13 
Math.Ceiling(123.12000000000001 * 100)/100 'display 123.12 
Math.Ceiling(123.12000000000002 * 100)/100 'display 123.13 

Я сделал тест в VB.NET, но он должен быть таким же в C#.

+1

некоторая форма прецизионных потерь – Andrey

+8

Это связано с тем, что значения с плавающей запятой по своей сути являются неточными. См., Например, http://stackoverflow.com/questions/753948/why-is-floating-point-arithmetic-in-c-sharp-imprecise –

+0

Это может быть связано с бинарными дробями (http: // с плавающей запятой .de/форматы/двоичные /). Поскольку float представлены двумя целыми числами в делении, не каждый float может использоваться. Это особенно характерно, когда у вас есть много нулей за разделителем с десятичной запятой, за которым следует 1 (как в ваших примерах). В этом случае поток округляется. Например, 123.12000000000001 может быть округлено до 123.12 до того, как оно будет передано потолочному методу. – Sascha

ответ

2

Это округление с плавающей запятой. C# анализирует 123.12000000000001 и 123.12 как имеющие одинаковое значение. 123.12000000000002 анализируется как следующий доступный двойной.

var bytes = BitConverter.ToString(BitConverter.GetBytes(123.12)); 
// outputs 48-E1-7A-14-AE-C7-5E-40 
var bytes1 = BitConverter.ToString(BitConverter.GetBytes(123.12000000000001)); 
// outputs 48-E1-7A-14-AE-C7-5E-40 
var bytes2 = BitConverter.ToString(BitConverter.GetBytes(123.12000000000002)); 
// outputs 49-E1-7A-14-AE-C7-5E-40 
-1

Метод Потолок возвращает следующий более высокий целочисленный эквивалент.

так потолка (123,01) = 124

& потолка (123,0) = 123

0

Это связано с плавающей точкой, а не округление Math.Ceiling по себе которое, поскольку значения с плавающей точкой не может представлять все значения со 100% точностью.

Ваш пример немного надуман в любом случае, потому что если вы попытаетесь ввести 123.12000000000001 в visual studio, это изменит его на 123.12, потому что он знает, что значение не может быть представлено как double.

Читайте об этом здесь: What Every Computer Scientist Should Know About Floating-Point Arithmetic (кстати, это не относится к .NET)

Чтобы исправить ваш вопрос, вы можете использовать десятичное значение, а не в два раза. Math.Ceiling имеет перегрузку, которая принимает десятичную (все эти отображений 123.13):

Debug.WriteLine(Math.Ceiling(123.121D * 100)/100) 
    Debug.WriteLine(Math.Ceiling(123.1200000000001D * 100)/100) 
    Debug.WriteLine(Math.Ceiling(123.12000000000001D * 100)/100) 
    Debug.WriteLine(Math.Ceiling(123.12000000000002D * 100)/100) 

ли это исправление уместно, конечно, зависит от того, какого уровня точности вам требуется.

+0

Не десятичная нотация 'M', а не' D'? – TheLethalCoder

+0

Нет, см. Https://msdn.microsoft.com/en-us/library/s9cz43ek.aspx?f=255&MSPPError=-2147217396 - нет упоминания о 'M' там, насколько я могу видеть –

+0

Я был мышление C# моя ошибка – TheLethalCoder

0

Ceiling возвращает число, переданное ему, если они являются целыми числами, или же следующее высшее целое число. Так 5.0 остается 5.0 но 5.00001 становится 6.0.

Таким образом, из примеров, следующие очевидны:

Math.Ceiling(123.121 * 100)/100 // Obtain 12312.1, next highest is 12313.0, then divide by 100 is 123.13 
Math.Ceiling(123.1200000000001 * 100)/100 // Likewise 
Math.Ceiling(123.12000000000002 * 100)/100 // Likewise 

Чем больше сбивает с толку одно:

Math.Ceiling(123.12000000000001 * 100)/100 //display 123.12 

Однако, давайте посмотрим на:

123.12000000000001 * 100 - 12312.0 // returns 0 

По сравнению с:

123.1200000000001 * 100 - 12312.0 // returns 1.09139364212751E-11 
123.12000000000002 * 100 - 12312.0 // returns 1.81898940354586E-12 

Последние два умножений имеют результаты, которые немного выше, чем 12312.0, поэтому в то время как (123.12000000000002 * 100).ToString() возвращается "12312" действительное число, произведенное 123.12000000000002 * 100 математически 12312.000000000002 как можно ближе double для 123.12000000000002 это является 123.1200000000000181898940354586 так это то, что работал.

Если вы привыкли только делать десятичную арифметику это может показаться странным, что 123.12000000000002 является «округлым» в 123.1200000000000181898940354586, но помните, что эти числа хранятся в терминах двоичных значений, и округление зависит от базы вы работаете.

Так что, хотя строковое представление не указывает на это, оно действительно немного выше, чем 12312, и поэтому его потолок 12313.

Между тем с 123.12000000000001 * 100, что математически 12312.000000000001, но как можно ближе к double123.12000000000001 является то, что он может вписаться в это 123.12. Таким образом, это то, что используется для умножения, и когда результат передается на последующий вызов Ceiling(), его результат равен 12312.