BigDecimal to SQL NUMBER: проверка значения больше точности
В моем приложении я обрабатываю числа как BigDecimal и сохраняю их как NUMBER(15,5). Теперь мне нужно правильно проверить на Java, соответствуют ли значения BigDecimal столбцу, чтобы я мог генерировать правильные сообщения об ошибках без выполнения SQL, перехвата исключений и проверки кода ошибки поставщика. Моя база данных - Oracle 10.3, и такие ошибки вызывают ошибку 1438.
После некоторого поиска в Google я не нашел такого кода для этого, поэтому я придумал свой собственный. Но я действительно недоволен этим кодом... простым, но в то же время достаточно простым, чтобы сомневаться в его правильности. Я проверил это со многими значениями, случайными и границами, и это, кажется, работает. Но так как я действительно плохо разбираюсь в числах, я хотел бы иметь более надежный и хорошо проверенный код.
//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 ответа
Ну, так как никто не придумал другого решения, я оставляю код как есть.
Я не смог сделать этот тест точности / масштаба неудачным, и он всегда соответствовал решению регулярных выражений, поэтому, возможно, оба они верны (я проверил границы и с более чем 5M случайно сгенерированными значениями). Я буду использовать решение точности / масштаба, так как оно более чем на 85% быстрее, и, если оно не получится, я заменю его.
Спасибо за ваши ответы Тони.
Мой предыдущий "ответ", все еще здесь для целей истории, но я ищу реальный ответ =)
Следующее регулярное выражение тоже поможет:
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, ноль, минимальное значение -.00001, минимальное значение, минимальное значение +.00001 и значения с 4, 5 и 6 значениями справа от десятичной точки. Есть, вероятно, еще много.
Если у вас есть те, кто в джунит, вы хорошо.