Java和SQL Server中的精度噩梦

Java和SQL Server中的精度噩梦,java,sql-server,rounding,precision,Java,Sql Server,Rounding,Precision,我一直在与Java和SQL Server中的精度噩梦作斗争,直到我不知道的时候。就我个人而言,我理解这一问题及其根本原因,但向全球一半的客户解释这一点是不可行的(至少对我而言) 情况是这样的。我在SQL Server中有两列—Qty INT和Price FLOAT。它们的值分别为-1250和10.8601,因此,为了获得总值,其数量*价格和结果为13575.124999998(在Java和SQL Server中)。没错。问题是,客户不想看到这个数字,他们只看到13575.125,仅此而已。在一个

我一直在与Java和SQL Server中的精度噩梦作斗争,直到我不知道的时候。就我个人而言,我理解这一问题及其根本原因,但向全球一半的客户解释这一点是不可行的(至少对我而言)

情况是这样的。我在SQL Server中有两列—Qty INT和Price FLOAT。它们的值分别为-1250和10.8601,因此,为了获得总值,其数量*价格和结果为13575.124999998(在Java和SQL Server中)。没错。问题是,客户不想看到这个数字,他们只看到13575.125,仅此而已。在一个地方,他们可以看到它的精度是2位小数,另一个是4位小数。当以4位小数显示时,数字是正确的-13575.125,但当以2位小数显示时,他们认为数字是错误的-13575.12-应改为13575.13

帮助。

使用。Float不是表示货币的合适类型。它将处理浮动,并且浮动将始终产生舍入误差

不要将浮点数据类型用于 价格。你应该用“钱”或 “小额钱”

以下是[MS SQL]的参考资料 数据类型][1]

[1] :

更正:使用十进制(19,4)


谢谢伊莎

对于存储货币金额,浮点值不是一种好方法。根据您的描述,我可能会将金额处理为长整数,其值为货币金额乘以10^5作为数据库存储格式


您需要能够处理不影响精度的计算量,因此这里再次强调浮点不是解决方法。如果账本中借贷总额相差1美分,则账本在财务人员眼中会失效,因此请确保您的软件在他们的问题域中运行,而不是在您的问题域中运行。如果无法将现有类用于货币金额,则需要构建自己的类,该类使用
amount*10^5
并根据仅用于输入和输出目的的精度设置格式。

您的问题在于使用的是浮动。在java端,您需要使用BigDecimal,而不是float或double,在SQL端,您需要使用Decimal(19,4)(或者Decimal(19,3),如果它有助于跳转到精度级别)。不要使用货币类型,因为SQL中货币类型的数学运算会导致截断,而不是舍入。数据存储为浮点类型(您说是不可更改的)这一事实并不影响这一点,您只需在对其进行数学运算之前,在第一次机会转换它


在您给出的特定示例中,您需要首先获得4位十进制精度数字,并根据具体情况将其放入BigDecimal或decimal(19,4)中,然后将其进一步四舍五入到2位十进制精度。然后(如果你是四舍五入的),你会得到你想要的结果。

如果你不能修复底层数据库,你可以像这样修复java:

import java.text.DecimalFormat;

public class Temp {

    public static void main(String[] args) {
        double d = 13575.124999999;
        DecimalFormat df2 = new DecimalFormat("#.##");
        System.out.println( " 2dp: "+ Double.valueOf(df2.format(d)) );

        DecimalFormat df4 = new DecimalFormat("#.####");
        System.out.println( " 4dp: "+Double.valueOf(df4.format(d)) );
    }
}

尽管你不应该把价格存储为<代码>浮点<代码>,但是你可以考虑把它转换成<代码>十进制(38, 4)< /代码>,或“代码>货币< /代码>(请注意,<代码>货币< /代码>由于其涉及的表达式的结果没有动态调整其规模)存在一些问题。并在退出SQL Server时将其显示在视图中:

SELECT Qty * CONVERT(decimal(38, 4), Price)

因此,如果您不能更改数据库结构(这可能是最好的选择,因为您使用的是非固定精度来表示应该是固定/精确的内容,正如许多其他人已经讨论过的),那么希望您可以在某个地方更改代码。在Java方面,我认为像@andy_boot这样的回答会起作用。在SQL方面,基本上需要将非精确值转换为所需的最高精度,然后继续向下转换,在SQL代码中基本上是这样的:

declare @f  float,
        @n  numeric(20,4),
        @m  money;

select  @f = 13575.124999999998,
        @n = 13575.124999999998,
        @m = 13575.124999999998

select  @f, @n, @m
select  cast(@f as numeric(20,4)), cast(cast(@f as numeric(20,4)) as numeric(20,2))
select  cast(@f as money), cast(cast(@f as money) as numeric(20,2))

我想我看到了问题所在

10.8601不能完美地表示出来,因此,虽然舍入到13575.125可以正常工作,但很难将其舍入到.13,因为添加0.005并不能完全达到这一点。更糟糕的是,0.005也没有一个精确的表示法,所以最终只差0.13一点点

然后,您可以选择两次四舍五入,一次四舍五入到三位数,然后一次四舍五入到2位数,或者先做一个更好的计算。使用长格式或高精度格式,按1000缩放以获得*.125到*125。使用精确整数进行舍入

顺便说一句,说“浮点不准确”的一个无休止重复的变体或者说它总是产生错误并不完全正确。问题是,该格式只能表示分数,您可以将二的负幂相加来创建分数。因此,在序列0.01到0.99中,只有.25、.50和.75具有精确的表示。因此,具有讽刺意味的是,FP的最佳使用方式是对其进行缩放,使其仅使用整数值,然后它与整数数据类型算术一样精确。当然,你也可以先用定点整数

小心,如果不四舍五入,0.37到37的比例仍然是不精确的。浮点可以用于货币值,但它的工作量超过了它的价值,而且通常不具备必要的专业知识。

您也可以先做一个测试,然后使用它进行四舍五入

DecimalFormat df = new DecimalFormat("0.00"); //or "0.0000" for 4 digits.
df.setRoundingMode(RoundingMode.HALF_UP);
String displayAmt = df.format((new Float(<your value here>)).doubleValue());
DecimalFormat df=新的DecimalFormat(“0.00”)//或“0.0000”表示4位数字。
df.设置舍入模式(舍入模式半向上);
字符串displayAmt=df.format((new Float()).doubleValue());

我同意其他人的观点,即不应使用Float作为DB字段类型来存储货币。

如果无法将数据库更改为固定的十进制数据类型,则可以尝试使用truncate((x+.0055)*10000)/10000进行舍入。然后1.124999将“四舍五入”到1.13,并给出一致的结果。从数学上讲,这是不可靠的,但我认为它在您的情况下会起作用。

浮点数据类型不能表示压裂