Почему CLng дает разные результаты?

Вот небольшая жемчужина прямо из моего VBE (MS Excel 2007 VBA):

?clng(150*0.85)
 127 
x = 150*0.85
?clng(x)
 128 

Кто-нибудь может объяснить такое поведение? ИМХО, первое выражение должно давать 128 (0,5 с округлением до ближайшего четного) или, по крайней мере, оба результата должны быть равны.


person Torben Klein    schedule 14.02.2011    source источник
comment
Странный. CLng(CDbl(150*0.85)) исправляет это. Round(150*0.85) тоже.   -  person wqw    schedule 14.02.2011
comment
Как и CSng. Там идет моя теория, что это была ошибка с плавающей запятой.   -  person Jonathan Allen    schedule 14.02.2011


Ответы (5)


Я думаю, что wqw прав, но я дам подробности.

В операторе clng(150 * 0.85) 150 * 0.85 вычисляется с повышенной точностью:

150 = 1.001011 x 2^7

0.85 с двойной точностью =

1.1011001100110011001100110011001100110011001100110011 x 2^-1

Умножьте их вручную, и вы получите

1.1111110111111111111111111111111111111111111111111111110001 x 2^6 =
127.4999999999999966693309261245303787291049957275390625

Это 59 бит, что удобно для расширенной точности. Это меньше 127.5, поэтому округляется в меньшую сторону.

В операторе x = 150 * 0.85 это 59-битное значение округляется до 53 бит, что дает

1.1111111 x 2^6 = 1111111.1 = 127.5

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

(См. мою статью http://www.exploringbinary.com/when-doubles-dont-behave-like-doubles/ для получения дополнительной информации.)

person Rick Regan    schedule 14.02.2011

Ах, одна из «забавных» вещей в VBA — это округление в CInt() и т. д., то, что называется округлением банкиров. Банковское округление — это округление значений 0,5 в большую или меньшую сторону в зависимости от того, является ли число четным, поэтому 2,5 округляется до 2, 3,5 до 4 и так далее.

Подробнее об округлении можно узнать здесь

http://www.consultdmw.com/rounding-numbers.htm

person Kevin Ross    schedule 14.02.2011
comment
Он упоминает, что это должно быть округлено до ближайшего даже в вопросе. - person Jonathan Allen; 14.02.2011
comment
В этом случае 127 не является ближайшим четным. Это ничего не объясняет. - person wqw; 14.02.2011

Это небольшое предположение, но .85 не может быть представлено как число с плавающей запятой. Если он отключен на 0,0000000000001, он все еще может странным образом влиять на округление.

Если вы используете CDec(.85), чтобы принудительно перевести его в десятичный режим, вы не получите этой странности. Это одна из многих причин, почему я не использую single/double там, где важна точность.

person Jonathan Allen    schedule 14.02.2011
comment
Первый результат может быть получен из-за ошибки округления (0,4999... вместо 0,5), но в этом случае второй результат также должен быть 127. Насколько я понимаю, промежуточное присвоение x не должно влиять на результат приведения. - person Torben Klein; 14.02.2011

Моя теория заключается в том, что VBA/VB6 использует x87 для вычислений с плавающей запятой, и это неявно преобразует двойные числа в более высокую точность, если 80 бит для промежуточных результатов. Таким образом, присваивание v или явное приведение с CDbl преобразует промежуточное 80-битное значение обратно в 64-битное, эффективно округляя его (или усекая).

Вот некоторые обсуждения:

Расширенный (80- бит) двойная плавающая точка в x87, а не в SSE2 — мы не пропустили?

person wqw    schedule 14.02.2011

И то, что сказали Кевин и Джонатан, верно, но ответ Джонатана здесь более уместен. Если бы вы имели дело с числами валютного типа вместо чисел с плавающей запятой, то применялось бы правило округления банкира.

person Steve Jorgensen    schedule 14.02.2011
comment
CLng ПРИМЕНЯЕТ БАНКИРОВСКОЕ Округление - person Charles Williams; 14.02.2011
comment
Да, но это не имеет значения, если то, к чему он применяет округление, уже является двоичным числом с плавающей запятой, которое включает ошибку округления по отношению к десятичному представлению. Число уже будет немного выше или ниже точки 1/2 пути, поэтому любое правило, которое алгоритм округления будет использовать для разрешения значений 1/2 пути, никогда не будет применяться. - person Steve Jorgensen; 14.02.2011