«Вокруг половины» на значениях с плавающей запятой - PullRequest
7 голосов
/ 03 марта 2010

Мы застряли в базе данных, которая (к сожалению) использует числа с плавающей запятой вместо десятичных значений. Это делает округление немного сложным. Рассмотрим следующий пример (SQL Server T-SQL):

SELECT ROUND(6.925e0, 2)   --> returns 6.92

ROUND делает округление до половины , но поскольку числа с плавающей запятой не могут точно представлять десятичные числа , «неправильный» результат (с точки зрения конечного пользователя ) отображается. Я понимаю , почему это происходит.

Я уже предложил два возможных решения (оба возвращают число с плавающей запятой, что, к сожалению, также является обязательным требованием):

  1. Преобразовать в десятичный тип данных перед округлением: SELECT CONVERT(float, ROUND(CONVERT(decimal(29,14), 6.925e0), 2))
  2. Умножьте до тех пор, пока третья цифра не окажется слева от десятичной точки (то есть точно представлена), а затем выполните округление: SELECT ROUND(6.925e0 * 1000, -1) / 1000

Какой из них выбрать? Есть ли лучшее решение? (К сожалению, мы не можем изменить типы полей в базе данных из-за того, что некоторые устаревшие приложения обращаются к одной и той же БД.)

Существует ли устоявшееся наилучшее практическое решение решение этой (распространенной?) Проблемы?

(Очевидно, что общая методика «округления в два раза» здесь не поможет, поскольку 6,925 уже округлено до трех знаков после запятой - насколько это возможно для числа с плавающей запятой.)

Ответы [ 4 ]

6 голосов
/ 03 марта 2010

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

Редактировать: Вам, вероятно, все еще нужно будет сделать дополнительный раунд (например, до 3 десятичных знаков или любого другого, подходящего для вашего приложения) сразу после получения значения с плавающей запятой и преобразования в десятичную, чтобы убедиться, что вы в конечном итоге получите десятичное значение, которое было на самом деле предназначено. 6.925e0 преобразованное в десятичное число снова вероятно (при условии, что десятичный формат имеет> 16 цифр точности), чтобы получить что-то, что очень близко, но не точно равно, 6.925; об этом позаботится дополнительный раунд.

Второе решение не выглядит для меня надежным: что, если из-за обычных двоичных проблем с плавающей запятой сохраненное значение для 6.925e0 окажется слишком маленьким? Затем после умножения на 1000 результат может все еще быть касанием ниже 6925, так что шаг округления округляется вниз, а не вверх. Если вы знаете, что ваше значение всегда имеет не более 3 цифр после точки, вы можете исправить это, выполнив дополнительный раунд после умножения на 1000, что-то вроде ROUND(ROUND(x * 1000, 0), -1).

(Отказ от ответственности: хотя у меня есть большой опыт работы с проблемами с плавающей запятой и десятичными числами в других контекстах, я почти ничего не знаю о SQL.)

2 голосов
/ 30 ноября 2015

Старый вопрос, но я удивлен, что здесь не упоминается обычная практика, поэтому я просто добавляю ее. Обычно вы добавляете небольшую сумму, которая, как вы знаете, намного меньше точности чисел, с которыми вы работаете, например, как это:

SELECT ROUND(6.925e0 + 1e-7, 2)

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

0 голосов
/ 26 мая 2012

Мне удалось правильно округлить плавающий столбец с помощью следующей команды:

ВЫБРАТЬ КОНВЕРТ.

0 голосов
/ 03 марта 2010

Используйте формат произвольной точности, такой как DECIMAL. Таким образом, вы можете оставить это на языке, чтобы понять это правильно (или неправильно в зависимости от обстоятельств).

...