Как округлить Double для точного битового представления? - PullRequest
1 голос
/ 01 июля 2019

Я делаю географические расчеты, и в итоге получаю широту и долготу для хранения в Geography::Point объекте.Широта и долгота могут иметь максимум 7 цифр (что также дает точность до 11 мм, что достаточно).

Проблема заключается в следующем: если значение поля не может быть правильно сохранено в Double, MS SQL округляет до ближайшего возможного числа, но делает это путем добавления набора цифр.
=> например, 5.9395772 сохраняется как 5.9395771999999996

Проблема, которую это создает, заключается в том, что [Position].ToString() превышает максимально допустимое количество символов для этого столбца (и нет, я не могу увеличить этот предел).
Поскольку мы имеем дело с Локатором,Долгота, Высота и Точность, есть место для ровно 11 символов для Широты и Долготы каждый:
String.Format(CultureInfo.InvariantCulture, "{0:##0.0######}", num)

Я пробовал просто Math.Round(), используя 6 цифр, но затем другие цифры (например, 6.098163 до 6.0981629999999996) получить ту же проблему.

Как мне Math.Round по отношению к ближайшему 7-значному допустимому биту представление?

РЕДАКТИРОВАТЬ/ ADD

    Public Function ToString_LatLon(ByVal num As Double) As String
        num = Math.Round(num, 7, MidpointRounding.AwayFromZero)
        Return String.Format(CultureInfo.InvariantCulture, "{0:##0.0######}", num) 
    End Function 'IN = 5.9395772, OUT = 5.9395772
  • Приведенный выше код получает Double and правильно возвращает представление String.Я проверил это, это верно и для тревожных чисел.

  • Он хранится в SQL Server через структуру, которую мы используем. Я думаю, что проблема возникает при сохранении значения

  • Когда я получаю значение, я получаю ошибку в VB, говоря, что значение шире, чем позволяет каркас (не более 50 символов).

  • Если я запускаю запрос в SSMS, я нахожу, например, POINT (X.0981629999999996 XX.664725 NULL 15602.707) (51 символ, анимированный).

РЕДАКТИРОВАТЬ 2

Я провел еще несколько исследований и некоторые вычисления.Кажется, что сохраненное значение 5.9395772 преобразуется в двоичный файл и возвращается как 5.9395771999999996, который хранится в виде двойного в базе данных (в двоичном объекте Geography::Point, не беспокойтесь.) Преобразование двоичного файла 0 10000000001 0111110000100010000010000110100010000100010011011101 обратнодо десятичного знака, и вы получите 5.93957719999999955717839839053340256214141845703125, но сокращенно до 16 десятичных знаков - тогда как я хотел бы, чтобы оно сокращалось до 7 десятичных знаков.

Решения:

  1. Округление значения вниз / вверх доближайшее значение, при котором все, начиная с 8-го знака после запятой, равно 0 (или достаточно нулей, прежде чем будет найдена другая ненулевая цифра)
  2. Запрос только о таком количестве десятичных знаков.
  3. Запрос фактического (шестнадцатеричного) значения,и преобразуйте это (вместо строкового представления)
  4. Сохраните строковое представление, но округлите значения перед сохранением и после получения до необходимого количества десятичных знаков.

Обсуждения:

  1. И в офисе, и у нас (ответ @ RobertBaron): это довольно сложно, может привести к значительному снижению точности и, в основном, много работы.
  2. Возможно, это возможно, я не знаю.
  3. Это было бы самое чистое решение, как и мои коллеги, и я согласен, однако это много работы вразработка и тестирование.
  4. Вместо того, чтобы заботиться о том, чтобы значение в памяти было равно значению в базе данных, нас не заботит значение в базе данных (слишком много).

В итоге, после нескольких вычислений на доске и долгого обсуждения, мы перешли к варианту 4 .После того, как мы извлекли [Position].ToString() (для которого мы увеличили предел строки) из базы данных, мы конвертируем это, как мы уже делаем, и в качестве дополнительного шага перед его использованием в любом месте округляем значение до необходимого количества десятичных дробей,Возвращая значение в базу данных, мы снова округляем значение до количества десятичных дробей, и нам все равно, что на самом деле делает с ним база данных.
По сути, это вариант 2, но затем на стороне программы.вместо базы данных.

Ответы [ 2 ]

1 голос
/ 01 июля 2019

Это только частичный ответ.

Если под допустимым битовым представлением вы подразумеваете точным битовым представлением, то это возможно. Десятичные числа, которые имеют точное представление битов: 1/2, 1/4, 3/4, 1/8, 3/8, 5/8, 7/8, 1/16, 3/16, ...

Задача состоит в том, чтобы охарактеризовать среди этих степеней двух тех, чьи базовые 10 представлений имеют 7 или менее цифр, а затем округлить любое базовое 10 до ближайшего из этих чисел.

Я публикую это в надежде, что это может сделать вас на шаг вперед к решению проблемы.

0 голосов
/ 01 июля 2019

Если по каким-либо причинам вы не можете изменить тип данных на DECIMAL, вы должны преобразовывать его в DECIMAL каждый раз, когда вам нужно это значение.Это так просто.И вы можете сделать это на стороне SQL Server или в VB.NET, но вам нужно DECIMAL.DOUBLEs неточны.

Кстати, SQL Server не округляет до ближайшего числа, которое он распознает, добавляя несколько цифр - это процессор , который делает это.Вот почему вы можете получить немного другие значения DOUBLE после восстановления базы данных на другом сервере.

И никогда даже не думать об их использовании в качестве идентификатора: я знаю приложение, которое использует значения FLOAT, содержащиеотметка времени (<creation day since whatever>.<time as fractals of the day>) как часть первичного ключа (почти каждой таблицы!).Каждая 10000-я запись или около того не может быть адресована напрямую по ее идентификатору, потому что значение на клиенте, отправляющем запрос, и на сервер несколько раз отличается на несколько наносекунд, хотя в SSMS на клиенте и сервере это число выглядит одинаково.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...