5

我一直在为 Java 和 SQL Server 中的精确噩梦而苦苦挣扎,直到我不再知道为止。就个人而言,我理解这个问题及其根本原因,但向全球另一端的客户解释这一点是不可行的(至少对我而言)。

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

帮助。

4

11 回答 11

15

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

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

于 2009-11-17T16:48:44.743 回答
5

使用BigDecimal。浮点数不是代表货币的合适类型。它将正确处理舍入。浮点数总是会产生舍入误差。

于 2009-11-17T16:42:22.350 回答
3

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

您需要能够处理不丢失精度的计算,所以这里再次浮点不是要走的路。如果账本中借方和贷方之间的总和减少了 1 美分,那么账本在财务人员眼中就失败了,因此请确保您的软件在他们的问题域中运行,而不是在您的问题域中运行。如果您不能将现有类用于货币金额,则需要构建自己的类,该类可以amount * 10^5根据仅用于输入和输出目的所需的精度进行处理和格式化。

于 2009-11-17T16:48:26.313 回答
1

不要将浮点数据类型用于价格。您应该使用“Money”或“SmallMoney”。

这是 [MS SQL DataTypes][1] 的参考。

[1]: http ://webcoder.info/reference/MSSQLDataTypes.html

更正:使用小数(19,4)

谢谢一夏。

于 2009-11-17T16:43:00.023 回答
1

我想我看到了问题。

10.8601 无法完美表示,因此虽然四舍五入到 13575.125 工作正常,但很难将其四舍五入到 0.13,因为添加 0.005 并不能完全达到。更糟糕的是,0.005 也没有精确的表示,所以你最终会略低于 0.13。

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

顺便说一句,说“浮点数不准确”的无休止重复变体之一或它总是产生错误并不完全正确。问题是该格式只能表示您可以将两个负幂相加来创建的分数。因此,在 0.01 到 0.99 的序列中,只有 0.25、0.50 和 0.75 具有精确的表示。因此,具有讽刺意味的是,最好使用 FP,通过缩放它以便只使用整数值,然后它与整数数据类型算术一样准确。当然,那么您也可能刚开始使用定点整数。

小心,缩放,比如说,0.37 到 37 仍然不准确,除非四舍五入。浮点数用于货币价值,但它的工作量大于其价值,并且通常没有必要的专业知识。

于 2009-11-17T16:54:52.953 回答
1

FLOAT 数据类型无法准确表示分数,因为它是 base2 而不是 base10。(参见方便的链接: ) http://gregs-blog.com/2007/12/10/dot-net-decimal-type-vs-float-type/)。

对于金融计算或任何需要准确表示分数的东西,必须使用 DECIMAL 数据类型。

于 2010-11-11T04:17:00.137 回答
0

如果你不能修复底层数据库,你可以像这样修复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)) );
    }
}
于 2009-11-17T16:49:04.167 回答
0

尽管您一开始不应该将价格存储为 a float,但您可以考虑将其转换为decimal(38, 4),例如,或money(请注意,money由于涉及它的表达式的结果没有动态调整其比例,因此存在一些问题),并公开在退出 SQL Server 的视图中:

SELECT Qty * CONVERT(decimal(38, 4), Price)
于 2009-11-17T16:53:06.607 回答
0

因此,鉴于您无法更改数据库结构(这可能是最好的选择,因为您正在使用非固定精度来表示应该是固定/精确的东西,正如许多其他人已经讨论过的那样),希望您可以在某处更改代码。在 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))
于 2009-11-17T16:53:22.427 回答
0

您也可以执行DecimalFormat然后使用它进行舍入。

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());

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

于 2009-11-17T17:07:07.343 回答
0

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

于 2009-11-17T17:10:08.663 回答