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% быстрее, и пусть оно не сработает, я его заменю.
Спасибо за ваши ответы Тони.
мой предыдущий "ответ", все еще здесь история целей, но я ищу реальный ответ =)