Зачем видеть -0,000000000000001 в запросе доступа? - PullRequest
0 голосов
/ 12 сентября 2010

У меня есть sql:

SELECT Sum(Field1), Sum(Field2), Sum(Field1)+Sum(Field2)
FROM Table
GROUP BY DateField
HAVING Sum(Field1)+Sum(Field2)<>0;

Проблема иногда в сумме полей field1 и field2, например: 9.5-10.3, а результат равен -0,800000000000001.Кто-нибудь может объяснить, почему это происходит и как это решить?

Ответы [ 3 ]

2 голосов
/ 21 сентября 2010

Проблема иногда в сумме полей1 и поле2 имеет значение типа: 9.5-10.3 и результат -0,800000000000001. Мог кто-нибудь объяснить, почему это происходит, и как это решить?

Почему это происходит

Типы float и double хранят числа в базе 2, а не в базе 10. Иногда число может быть точно представлено конечным числом битов.

9.5 → 1001.1

А иногда не может.

10.3 → 1010.0 1001 1001 1001 1001 1001 1001 1001 1001...

В последнем случае число округляется до ближайшего значения, которое может быть представлено как double:

1010.0100110011001100110011001100110011001100110011010 base 2
= 10.300000000000000710542735760100185871124267578125 base 10

Когда вычитание выполняется в двоичном виде, вы получаете:

-0.11001100110011001100110011001100110011001100110100000
= -0.800000000000000710542735760100185871124267578125

Процедуры вывода обычно скрывают большинство цифр «шума».

  • Python 3.1 округляет его до -0.8000000000000007
  • SQLite 3.6 округляет его до -0.800000000000001.
  • printf %g округляет до -0.8.

Обратите внимание, что даже в системах, в которых значение равно -0,8, оно не совпадает с наилучшим приближением double к -0,8, а именно:

- 0.11001100110011001100110011001100110011001100110011010
= -0.8000000000000000444089209850062616169452667236328125

Таким образом, на любом языке программирования, использующем double, выражение 9.5 - 10.3 == -0.8 будет ложным.

decimal не решение

С такими вопросами наиболее распространенным ответом является "использовать десятичную арифметику". Это действительно дает лучший результат в этом конкретном примере. Использование класса Python decimal.Decimal:

>>> Decimal('9.5') - Decimal('10.3')
Decimal('-0.8')

Однако вам все равно придется иметь дело с

>>> Decimal(1) / 3 * 3
Decimal('0.9999999999999999999999999999')
>>> Decimal(2).sqrt() ** 2
Decimal('1.999999999999999999999999999')

Это могут быть более знакомые ошибки округления, чем у двоичных чисел, но это не делает их менее важными .

Фактически, двоичные дроби больше точнее, чем десятичные дроби с одинаковым количеством битов, из-за комбинации:

Это также намного быстрее (на ПК), потому что оно имеет выделенное оборудование.

В базовой десятке нет ничего особенного. Это просто произвольный выбор, основанный на количестве пальцев, которые у нас есть.

Было бы точно так же точно сказать, что новорожденный ребенок весит 0x7,5 фунта (в более знакомых терминах, 7 фунтов 5 унций), чтобы сказать, что он весит 7,3 фунта. (Да, разница в 0,2 унции между два, но это в пределах допуска.) В общем, десятичная дробь не дает никаких преимуществ в представлении физических измерений.

Деньги разные

В отличие от физических величин, которые измеряются с определенным уровнем точности, деньги считаются и, следовательно, точной величиной. Причудой является то, что она считается в кратных 0,01 вместо кратных 1, как и большинство других дискретных величин.

Если ваш «10,3» действительно означает $ 10,30, то вы должны использовать тип десятичного числа для точного представления значения.

(Если вы не работаете с историческими ценами на акции с тех дней, когда они были в 1/16 доллара, в этом случае двоичный код в любом случае адекватен ;-))

В противном случае это просто проблема с отображением.

Вы получили правильный ответ до 15 значащих цифр. Это верно для всех практических целей. Если вы просто хотите скрыть «шум», используйте функцию SQL ROUND.

1 голос
/ 12 сентября 2010

Я уверен, что это потому, что тип данных с плавающей запятой (он же Double или Single в MS Access) является неточным. Это не десятичное число, которое является простым значением, масштабируемым степенью 10. Если я правильно помню, значения с плавающей запятой могут иметь разные знаменатели, что означает, что они не всегда точно преобразуются обратно в основание 10.

Лечение состоит в том, чтобы изменить Field1 и Field2 с плавающей / одинарной / двойной на десятичную или валюту Если вы приведете примеры наименьших и наибольших значений, которые вам нужно хранить, включая наименьшие и наибольшие необходимые дроби, такие как 0,0001 или 0,9999, мы, возможно, посоветуем вам лучше.

Имейте в виду, что версии Access до 2007 года могут иметь проблемы с ORDER BY для десятичных значений. Пожалуйста, прочитайте комментарии к этому посту, чтобы узнать больше об этом. Во многих случаях это не будет проблемой для людей, но в других случаях это может быть.

Как правило, float следует использовать для значений, которые могут оказаться очень маленькими или большими (меньшими или большими, чем может содержать десятичная дробь). Вы должны понимать, что float поддерживает более точный масштаб за счет некоторой точности. То есть десятичная дробь будет переполнена или переполнена, когда число с плавающей запятой может просто продолжаться. Но число с плавающей запятой имеет ограниченное количество значащих цифр, тогда как все десятичные числа значимы.

Если вы не можете изменить типы столбцов, то тем временем вы можете обойти проблему, округлив окончательный расчет. Не округляйте до самого последнего возможного момента.

Обновление

Критика моей рекомендации по использованию десятичной дроби была нивелирована, но не в отношении неожиданных результатов ORDER BY, но это значение с плавающей точкой в ​​целом более точное с тем же числом битов.

Нет оспаривания этого факта. Тем не менее, я думаю, что люди чаще работают с ценностями, которые на самом деле учитываются или должны быть выражены в базовой десятке. Я снова и снова вижу вопросы на форумах о том, что не так с их типами данных с плавающей точкой, и я не вижу те же самые вопросы о десятичной дроби. Для меня это означает, что люди должны начинать с десятичной дроби, и когда они будут готовы к прыжку к тому, как и когда использовать float, они могут изучить его и начать использовать, когда они компетентны.

В то же время, хотя может быть немного неприятно, когда люди всегда рекомендуют десятичную , когда вы знаете, что она не столь точна , не позволяйте себе отделиться от реального мира, где более знакомое округление ошибки за счет очень незначительного снижения точности имеют значение.

Позвольте мне указать моим хулителям, что пример

Decimal(1) / 3 * 3 выход 1.999999999999999999999999999

- это, как следует из знакомых слов, «исправить до 27 значащих цифр», что «правильно для всех практических целей».

Так что, если у нас есть два способа сделать то, что практически говорит одно и то же, и оба они могут очень точно представлять числа до смехотворного числа значащих цифр, и оба требуют округления, но один из них имеет заметно более знакомые ошибки округления, чем другие, я не могу согласиться с тем, что рекомендация более знакомой в любом случае плоха. Что делать новичку в системе, которая может выполнить a - a и не получить 0 в качестве ответа? У него будет путаница, и он будет остановлен в своей работе, пока он пытается это понять. Затем он обратится за помощью на доску объявлений и получит ответ на вопрос "используйте десятичную дробь". Тогда он будет просто в порядке еще пять лет, пока он не вырастет настолько, что однажды ему станет любопытно, и он, наконец, изучит и действительно поймет, что делает поплавок, и сможет использовать его правильно.

Тем не менее, в конечном счете, я должен сказать, что, отбрасывая меня за рекомендацию десятичной дроби, кажется, немного в космическом пространстве.

Наконец, я хотел бы отметить, что следующее утверждение не является строго верным, так как оно обобщенно:

Числа типа float и double хранятся в базе 2, а не в базе 10.

Если быть точным, большинство современных систем хранят типы данных с плавающей точкой с базой 2. Но не все! Некоторые используют или использовали базу 10. Насколько я знаю, существуют системы, которые используют базу 3, которая ближе к e и, таким образом, имеет более оптимальную экономию радиуса, чем представления базы 2 (как если бы это действительно имело значение для 99,999% всех пользователей компьютеров). Кроме того, высказывание «float и double types» может быть немного вводящим в заблуждение, так как double IS float, но float не double. Float - это сокращение от плавающей запятой, но Single и Double являются числами с плавающей запятой подтипов , которые обозначают общую доступную точность. Существуют также типы данных с плавающей запятой Single-Extended и Double-Extended.

1 голос
/ 12 сентября 2010

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

Исправление будет заключаться в использовании функции округления значений для обрезания посторонних цифр. Вот так (я просто округлил до 4 значащих цифр после десятичной дроби, но, конечно, вы должны использовать любую точность, подходящую для ваших данных):

SELECT Sum(Field1), Sum(Field2), Round(Sum(Field1)+Sum(Field2), 4)
FROM Table
GROUP BY DateField
HAVING Round(Sum(Field1)+Sum(Field2), 4)<>0;
...