Java 和 SQL Server 中的精度噩梦

发布于 2024-08-11 02:53:41 字数 429 浏览 3 评论 0原文

我一直在与 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!

帮助。

I've been struggling with precision nightmare in Java and SQL Server up to the point when I don't know anymore. Personally, I understand the issue and the underlying reason for it, but explaining that to the client half way across the globe is something unfeasible (at least for me).

The situation is this. I have two columns in SQL Server - Qty INT and Price FLOAT. The values for these are - 1250 and 10.8601 - so in order to get the total value its Qty * Price and result is 13575.124999999998 (in both Java and SQL Server). That's correct. The issue is this - the client doesn't want to see that, they see that number only as 13575.125 and that's it. On one place they way to see it in 2 decimal precision and another in 4 decimals. When displaying in 4 decimals the number is correct - 13575.125, but when displaying in 2 decimals they believe it is wrong - 13575.12 - should instead be 13575.13!

Help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

欢你一世 2024-08-18 02:53:42

不要使用浮点数据类型
价格。您应该使用“金钱”或
“小钱”。

这里是 [MS SQL
数据类型][1]。

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

更正:使用 Decimal(19, 4)

谢谢伊莎。

Don't use the float datatype for
price. You should use "Money" or
"SmallMoney".

Here's a reference for [MS SQL
DataTypes][1].

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

Correction: Use Decimal(19,4)

Thanks Yishai.

仲春光 2024-08-18 02:53:42

我想我看到了问题所在。

10.8601 无法完美表示,因此虽然四舍五入到 13575.125 的效果很好,但很难让它四舍五入到 0.13,因为添加 0.005 并不能完全实现这一点。更糟糕的是,0.005 也没有精确的表示,所以最终的结果只是略低于 0.13。

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

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

请注意,除非四舍五入,否则缩放(例如 0.37 到 37)仍然不准确。浮点可以用于货币值,但其工作量超出其价值,而且通常不具备必要的专业知识。

I think I see the problem.

10.8601 cannot be represented perfectly, and so while the rounding to 13575.125 works OK it's difficult to get it to round to .13 because adding 0.005 just doesn't quite get there. And to make matters worse, 0.005 doesn't have an exact representation either, so you end up just slightly short of 0.13.

Your choices are then to either round twice, once to three digits and then once to 2, or do a better calculation to start with. Using long or a high precision format, scale by 1000 to get *.125 to *125. Do the rounding using precise integers.

By the way, it's not entirely correct to say one of the endlessly repeated variations on "floating point is inaccurate" or that it always produces errors. The problem is that the format can only represent fractions that you can sum negative powers of two to create. So, of the sequence 0.01 to 0.99, only .25, .50, and .75 have exact representations. Consequently, FP is best used, ironically, by scaling it so that only integer values are used, then it is as accurate as integer datatype arithmetic. Of course, then you might as well have just used fixed point integers to start with.

Be careful, scaling, say, 0.37 to 37 still isn't exact unless rounded. Floating point can be used for monetary values but it's more work than it is worth and typically the necessary expertise isn't available.

Oo萌小芽oO 2024-08-18 02:53:42

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

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

The FLOAT data type can't represent fractions accurately because it is base2 instead of base10. (See the convenient link :) http://gregs-blog.com/2007/12/10/dot-net-decimal-type-vs-float-type/).

For financial computations or anything that requires fractions to be represented accurately, the DECIMAL data type must be used.

迷你仙 2024-08-18 02:53:42

如果你无法修复底层数据库,你可以像这样修复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)) );
    }
}

If you can't fix the underlying database you can fix the java like this:

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)) );
    }
}
寂寞花火° 2024-08-18 02:53:42

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

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

Although you shouldn't be storing the price as a float in the first place, you can consider converting it to decimal(38, 4), say, or money (note that money has some issues since results of expressions involving it do not have their scale adjusted dynamically), and exposing that in a view on the way out of SQL Server:

SELECT Qty * CONVERT(decimal(38, 4), Price)
南笙 2024-08-18 02:53:42

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

So, given that you can't change the database structure (which would probably be the best option, given that you are using a non-fixed-precision to represent something that should be fixed/precise, as many others have already discussed), hopefully you can change the code somewhere. On the Java side, I think something like @andy_boot answered with would work. On the SQL side, you basically would need to cast the non-precise value to the highest precision you need and continue to cast down from there, basically something like this in the SQL code:

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))
短叹 2024-08-18 02:53:42

您还可以执行 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 作为数据库字段类型来存储货币。

You can also do a DecimalFormat and then round using it.

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

And I agree with others that you should not be using Float as a DB field type to store currency.

困倦 2024-08-18 02:53:42

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

If you can't change the database to a fixed decimal datatype, something you might try is rounding by taking truncate((x+.0055)*10000)/10000. Then 1.124999 would "round" to 1.13 and give consistent results. Mathematically this is unreliable, but I think it would work in your case.

成熟的代价 2024-08-18 02:53:41

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

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

Your problem is that you are using floats. On the java side, you need to use BigDecimal, not float or double, and on the SQL side you need to use Decimal(19,4) (or Decimal(19,3) if it helps jump to your precision level). Do not use the Money type because math on the Money type in SQL causes truncation, not rounding. The fact that the data is stored as a float type (which you say is unchangeable) doesn't affect this, you just have to convert it at first opportunity before doing math on it.

In the specific example you give, you need to first get the 4 decimal precision number and put it in a BigDecimal or Decimal(19,4) as the case may be, and then further round it to 2 decimal precision. Then (if you are rounding up) you will get the result you want.

梦幻的心爱 2024-08-18 02:53:41

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

Use BigDecimal. Float is not an approciate type to represent money. It will handle the rounding properly. Float will always produce rounding errors.

懷念過去 2024-08-18 02:53:41

对于存储货币金额,浮点值不是正确的选择。根据您的描述,我可能会将金额处理为长整数,并将货币金额乘以 10^5 作为数据库存储格式。

您需要能够处理不失去精度的金额的计算,因此浮点数也不是正确的选择。如果账本中借方和贷方之间的总金额相差 1 美分,那么在财务人员眼中,账本就会失败,因此请确保您的软件在他们的问题域中运行,而不是在您的问题域中运行。如果您无法使用现有的类来表示货币金额,则需要构建自己的类,该类可使用 amount * 10^5 并根据仅用于输入和输出目的所需的精度进行格式设置。

For storing monetary amounts floating point values are not the way to go. From your description I would probably handle amounts as long integers with as value the monetary amount multiplied by 10^5 as database storage format.

You need to be able to handle calculations with amounts that do not loose precision, so here again floating point is not the way to go. If the total sums between debit and credit are off by 1 cent in a ledger, the ledger fails in the eyes of financial people, so make sure your software operates in their problem domain, not yours. If you can not use existing classes for monetary amounts, you need to build your own class that works with amount * 10^5 and formats according to the precision wanted only for input and output purposes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文