【问题标题】:BigDecimal to SQL NUMBER: check for value larger than precisionBigDecimal 到 SQL NUMBER:检查值是否大于精度
【发布时间】:2011-04-15 22:10:33
【问题描述】:

在我的应用程序中,我将数字处理为 BigDecimal,并将它们存储为 NUMBER(15,5)。现在我需要在 Java 上正确检查 BigDecimal 值是否适合该列,这样我就可以在不执行 SQL、捕获异常和验证供应商错误代码的情况下生成正确的错误消息。我的数据库是Oracle 10.3,这样的错误导致error 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 方法)。抛开异常不谈,此测试方法必须确保数字对于所需的精度来说不会太大,即使是非有效数字也是如此。抱歉,澄清晚了。

感谢您的宝贵时间。


我仍然对这个问题很好奇。我的代码仍在运行,我还没有一些正确或失败情况的“证明”,也没有一些用于此类测试的标准代码。

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

【问题讨论】:

  • 我想到的唯一不同的事情是使用 toString 然后拆分 BD 并比较字符串的长度,但我认为你的方式比这更好。
  • 是的,我这样做是为了用随机值测试这个函数数万次......他们总是同意响应(只要我在这个字符串操作版本中去除减号信号)。尽管如此,恕我直言,这是有偏见的测试。

标签: java oracle oracle10g bigdecimal ora-01438


【解决方案1】:

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

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

【讨论】:

  • 我确实对它们进行了测试,结果还可以。尽管如此,我还是害怕将此功能标记为“正确”。并不是说我喜欢把事情复杂化,但感觉我没有正确地涵盖所有的输入。
【解决方案2】:

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

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"));
    }
}

【讨论】:

  • 我不想为此使用正则表达式,因为 BigDecimal 也接受指数(123.45e-1),并且处理它看起来很混乱......这正是我最奇怪的指数符号案例。不过谢谢你的建议。
  • 很好地抓住了指数错误。我将BigDecimal().toString 更改为.toPlainString(),现在指数问题得到了处理。我在上面添加了代码更改和新的测试用例;-) 正则表达式有一个简单而残酷的诚实。然而,这可能不是适合它的工作。我相信“代码完成”一书建议,在测试时,使用两种不同的技术对复杂的任务进行编码——如果他们的答案不同,你就有了一个错误。而且您担心代码的正确性...
  • 嘿,我对包含测试用例的单线坚如磐石的解决方案投了反对票。艰难的人群!
【解决方案3】:

好吧,既然没有人想出其他解决方案,我将保留代码原样。

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

感谢您的回复,托尼。


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

【讨论】:

    【解决方案4】:

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

    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),它们不应该都返回相同的结果吗?

    【讨论】:

    • 这很有趣,谢谢。但是,重点是防止数据库错误...... DBMS 会在这种情况下引发异常(0.000010)吗?如果是这样,那么我也必须拒绝它,无论是否删除非有效数字精度都很好。我可能会尽快测试它,我会尽快发布结果。
    • @mdrg:如果您尝试插入精度过高的数字,Oracle 不会抛出错误 :) 它会默默地将其四舍五入以适合列(.000011 将四舍五入为 .00001) .请参阅我的更新答案。
    • 这太糟糕了,IMO。无论如何,现在我不确定我是如何得到这个 ora-01438 错误的,因为我可以存储和重现舍入行为,这对我来说仍然是不可接受的,因为我们的应用程序是财务的。我将编辑包括那个在内的问题正文。
    • @mdrg:当列不能容纳插入的值(例如,在您的情况下为 1e11)时,会引发 ORA-01438。静默舍入行为可能会让您感到惊讶,但实际上它是许多语言的常见行为:在 java 中 int i = 7/2; 不会引发错误,并且会静默舍入到 3。
    • 整数除法不是四舍五入,严格来说,但我知道你的意思。我认为这两种情况下的错误都会增加(精度太大或规模太大),感谢您的澄清。在我的应用中,最好的解决方案是拒绝两者。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多