BigDecimal to SQL NUMBER: проверьте значение больше точности

в моем приложении я обрабатываю числа как BigDecimal и храню их как число(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;
}

Edit: последние тесты не получили исключения для чисел вне масштаба, просто молча округлились, и я не уверен, что отличается между Не И когда я сделал эти первые тесты. Такое округление недопустимо поскольку приложение является финансовым, и любое округление / усечение должно быть явным (через BigDecimal методы). Исключение-ушло в сторону, этот метод теста должен уверить что номер не слишком большой для пожеланной точности, даже если не-значными цифрами. Извините за позднее разъяснение.

Спасибо за ваше время.


Мне все еще любопытен этот вопрос. Мой код все еще работает, и у меня нет "доказательства" правильности или ситуации сбоя, или стандартный код для такого теста.

Итак, я назначаю награду за это, надеюсь получить любой из них.

4 ответов


следующий regexp тоже сделает трюк:

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 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) не должны ли они оба возвращать один и тот же результат?


вместо этого, если цикл более тысячи случайных чисел, вы можете написать тестовые случаи, которые подчеркивают "ребра" - максимальное значение +.00001, максимальное значение, максимальное значение - .00001, 0, null, минимальное значение -.00001, минимальное значение, минимальное значение + .00001, и значения со значениями 4, 5 и 6 справа от десятичной запятой. Вероятно, их гораздо больше.

Если у вас есть такие в JUnit, вы хорошо.


Ну, поскольку никто не придумал другого решения, я оставляю код как есть.

Я не мог сделать этот тест точности/масштаба неудачным, и он всегда соответствовал решению regex, поэтому, возможно, оба правильны (я проверил границы и с более чем 5M случайно сгенерированными значениями). Я буду использовать решение precision / scale, так как оно на 85% быстрее, и пусть оно не сработает, я его заменю.

Спасибо за ваши ответы Тони.


мой предыдущий "ответ", все еще здесь история целей, но я ищу реальный ответ =)