如何将Double的舍入舍入为精确的位表示形式?

时间:2019-07-01 14:24:34

标签: sql-server vb.net bit-manipulation

我正在进行地理计算,最终以纬度和经度存储在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.0981636.0981629999999996)也遇到了同样的问题。

我如何Math.Round向最接近的7位有效位表示法表示?

编辑/添加

    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,并正确返回String表示形式。我已经检查过了,这对于数字打扰也是正确的。

  • 它通过我们使用的框架存储在SQL Server中。 我认为存储值时会出现问题

  • 检索值时,在VB中出现错误,表示该值比框架允许的宽度宽(最多50个字符)。

  • 如果我在SSMS中运行查询,则会发现例如POINT (X.0981629999999996 XX.664725 NULL 15602.707)(51个字符,已停用)。

编辑2

我做了更多的研究和一些计算。似乎已存储的值5.9395772转换为二进制并以5.9395771999999996的形式返回,该值作为double存储在数据库中(在二进制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 个答案:

答案 0 :(得分:1)

这只是部分答案。

如果用有效位表示的意思是精确位表示,那么这是可能的。具有确切位表示形式的十进制数字为1 / 2、1 / 4、3 / 4、1 / 8、3 / 8、5 / 8、7 / 8、1 / 16、3 / 16,...

面临的挑战是在以10为基数表示7位或更少的数字的两个幂之间进行表征,然后将任何以10为基数的数字四舍五入到这些数字中最接近的数字。

我发布此邮件是希望它可以使您更进一步地寻求解决方案。

答案 1 :(得分:0)

如果由于某种原因无法将数据类型更改为DECIMAL,则每次需要该值时都必须将其转换为DECIMAL。就这么简单。您可以在SQL Server端或VB.NET中进行此操作,但是需要DECIMALDOUBLEs不精确。

顺便说一句,不是SQL Server通过添加一堆数字将其舍入到最接近的数字,而是由处理器完成。这就是为什么在另一台服务器上还原数据库后,您可能会得到稍微不同的DOUBLE值的原因。

甚至从来没有想过将它们用作ID:我知道一个应用程序将包含时间戳记(FLOAT的{​​{1}}值用作(几乎每个表的)主键的一部分。 。第10000条左右的记录不能直接通过其ID进行寻址,因为在发送查询的客户端和服务器上,该值有些不同,尽管该数字在客户端和服务器上的SSMS中看起来完全相同。