10

在我的应用程序中,我将数字处理为 BigDecimal,并将它们存储为 NUMBER(15,5)。现在我需要在 Java 上正确检查 BigDecimal 值是否适合该列,这样我就可以在不执行 SQL、捕获异常和验证供应商错误代码的情况下生成正确的错误消息。我的数据库是 Oracle 10.3,这样的错误会导致错误 1438

经过一番谷歌搜索,我发现没有这样的代码,所以我想出了自己的。但我对这段代码真的很不满意......简单,但同时简单到足以怀疑它的正确性。我用许多值、随机值和边界对其进行了测试,它似乎有效。但由于我对数字非常不满意,我想要一些更健壮且经过良好测试的代码。

//no constants for easier reading
public boolean testBigDecimal(BigDecimal value) {
    if (value.scale() > 5)
        return false;
    else if (value.precision() - value.scale() > 15 - 5)
        return false;
    else
        return true;
}

编辑:最近的测试并没有因为超出比例的数字而出现异常,只是默默地四舍五入,我不确定在没有和我进行这些第一次测试时有什么不同。这种舍入是不可接受的,因为应用程序是财务应用程序,并且任何舍入/截断必须是显式的(通过 BigDecimal 方法)。抛开异常不谈,此测试方法必须确保数字对于所需精度来说不会太大,即使是非有效数字也是如此。抱歉,澄清晚了。

谢谢你的时间。


这个问题我还是很好奇的。我的代码仍在运行,我还没有一些正确或失败情况的“证明”,或者这种测试的一些标准代码。

所以,我正在悬赏它,希望能得到其中的任何一个。

4

4 回答 4

5

以下正则表达式也可以解决问题:

public class Big {
    private static final Pattern p = Pattern.compile("[0-9]{0,10}(\\.[0-9]{0,5}){0,1}");

    public static void main(String[] args) {
        BigDecimal b = new BigDecimal("123123.12321");
        Matcher m = p.matcher(b.toString());
        System.out.println(b.toString() + " is valid = " + m.matches());
    }
}

这可能是测试代码的另一种方式,也可能代码。正则表达式需要 0 到 10 位数字,可选地后跟小数点和 0 到 5 位数字。当我想到它时,我不知道是否需要一个标志。将类似的东西[+-]{0,1}放在前面就可以了。

也许这是一个更好的类,以及一个带有部分测试集的测试类。

public class Big {
    private static final Pattern p = Pattern.compile("[0-9]{0,10}(\\.[0-9]{0,5}){0,1}");

public static boolean isValid(String s) {
    BigDecimal b = new BigDecimal(s);
    Matcher m = p.matcher(b.toPlainString());
    return m.matches();
    }
}

package thop;

import junit.framework.TestCase;

/**
 * Created by IntelliJ IDEA.
 * User: tonyennis
 * Date: Sep 22, 2010
 * Time: 6:01:15 PM
 * To change this template use File | Settings | File Templates.
 */
public class BigTest extends TestCase {

    public void testZero1() {
        assertTrue(Big.isValid("0"));
    }

    public void testZero2() {
        assertTrue(Big.isValid("0."));
    }

    public void testZero3() {
        assertTrue(Big.isValid("0.0"));
    }

    public void testZero4() {
        assertTrue(Big.isValid(".0"));
    }

    public void testTooMuchLeftSide() {
        assertFalse(Big.isValid("12345678901.0"));
    }

    public void testMaxLeftSide() {
        assertTrue(Big.isValid("1234567890.0"));
    }

    public void testMaxLeftSide2() {
        assertTrue(Big.isValid("000001234567890.0"));
    }

    public void testTooMuchScale() {
        assertFalse(Big.isValid("0.123456"));
    }

    public void testScientificNotation1() {
        assertTrue(Big.isValid("123.45e-1"));
    }

    public void testScientificNotation2() {
        assertTrue(Big.isValid("12e4"));
    }
}
于 2010-09-22T21:56:58.217 回答
3

您的功能的问题之一是在某些情况下它可能过于严格,请考虑:

BigDecimal a = new BigDecimal("0.000005"); /* scale 6 */
a = a.multiply(new BigDecimal("2")); /* 0.000010 */
return testBigDecimal(a); /* returns false */

如您所见,比例没有向下调整。如果高端精度(1e11/2)发生类似情况,我现在无法测试。

我建议更直接的路线:

public boolean testBigDecimal(BigDecimal value) {
    BigDecimal sqlScale = new BigDecimal(100000);
    BigDecimal sqlPrecision = new BigDecimal("10000000000");
    /* check that value * 1e5 is an integer */
    if (value.multiply(sqlScale)
          .compareTo(value.multiply(sqlScale)
              .setScale(0,BigDecimal.ROUND_UP)) != 0)
        return false;
    /* check that |value| < 1e10 */
    else if (value.abs().compareTo(sqlPrecision) >= 0)
        return false;
    else
        return true;
}

更新

您在评论中询问如果我们尝试插入 0.000010,数据库是否会抛出错误。事实上,如果您尝试插入一个精度过高的值,数据库将永远不会抛出错误,它会默默地舍入插入的值。

因此,不需要第一次检查来避免 Oracle 错误,我假设您正在执行此测试以确保您要插入的值等于您实际插入的值。由于 0.000010 和 0.00001 相等(带有BigDecimal.compareTo),它们不应该都返回相同的结果吗?

于 2011-02-15T17:43:29.513 回答
1

好吧,由于没有人想出另一种解决方案,我将保留代码原样。

我不能让这个精度/规模测试失败,它总是匹配正则表达式解决方案,所以也许两者都是正确的(我测试了边界和超过 5M 的随机生成值)。我将使用精度/比例解决方案,因为它的速度提高了 85% 以上,并且可能会失败我替换它。

感谢您的回复托尼。


我以前的“答案”,出于历史目的仍然在这里,但我正在寻找一个真正的答案=)

于 2010-09-24T12:23:16.113 回答
1

相反,如果循环数千个随机数,您可以编写强调“边缘”的测试用例 - 最大值 +.00001,最大值,最大值 - .00001,0,null,最小值 -.00001,最小值、最小值 + .00001 以及小数点右侧有 4、5 和 6 个值的值。可能还有更多。

如果你有这些在junit,你很好。

于 2010-09-22T21:44:39.203 回答