Обнаружение неточности Java NumberFormat

Я хочу точно проанализировать целое число, которое потенциально было отформатировано в соответствии с текущей локалью. Если я не проанализировал целое число точно, я хочу это знать. Поэтому я использую:

String string = "1111122222333334444455555";
Locale locale = Locale.getDefault();
NumberFormat numberFormat = NumberFormat.getIntegerInstance(locale);
numberFormat.setParseIntegerOnly();
Number number = numberFormat.parse(string);

Очевидно, что "1111122222333334444455555" представляет собой большое число, большее, чем может обработать Long. Итак, NumberFormat дает мне... Double??

Думаю, я ожидал получить BigInteger, а не Double, тем более, что я попросил форматирование целых чисел. Но это неважно; большая проблема заключается в том, что двойное значение, которое я возвращаю, равно 1.1111222223333344E24! Это не равно 1111122222333334444455555!!

Если NumberFormat дает мне проанализированное значение, которое не равно значению, хранящемуся во входной строке, как мне это определить?

Другими словами: «Как я могу узнать, точно ли значение Double, которое я получаю от NumberFormat, эквивалентно целочисленному значению, представленному в исходной строке?»


person Garret Wilson    schedule 29.06.2012    source источник
comment
Если вы используете простой Long.parseLong(), вы получите исключение переполнения.   -  person Marko Topolnik    schedule 29.06.2012
comment
Почему бы просто не построить BigInteger с входной строкой вместо того, чтобы пытаться ее анализировать?   -  person Conor Sherman    schedule 29.06.2012
comment
@MarkoTopolnik, мне нужно поддерживать значения, которые, возможно, были отформатированы в текущем локальном формате (например, 1234 в США). Я обновил вопрос, чтобы отразить это.   -  person Garret Wilson    schedule 29.06.2012
comment
@ConorSherman В большинстве случаев я работаю с Long. Я не хочу, чтобы меня заставляли использовать BigInteger, если этого не требует номер. Кроме того, конструктор BigInteger не обращает внимания на локаль (см. обновленный вопрос).   -  person Garret Wilson    schedule 29.06.2012
comment
Ты холодным еще считай. Если вам нужно проверить, что он соответствует правилам локали, это одно, но вы все равно можете легко проанализировать его, отфильтровав все, кроме цифр.   -  person Marko Topolnik    schedule 29.06.2012
comment
Или почему бы просто не проверить, что возвращаемое значение равно Long? Если это не признак переполнения, не так ли?   -  person Marko Topolnik    schedule 29.06.2012
comment
@MarkoTopolnik ... вы все равно можете легко разобрать его, отфильтровав все, кроме цифр. Нет, это неправильно --- я не могу просто игнорировать нецифры. Игнорирование запятой в строковом значении "1,234" в локали en_US даст целое число 1234, конечно, но в локали fr_FR мне нужно будет определить, что "1,234" вообще не является допустимым целым числом (это значение с плавающей запятой 1.234).   -  person Garret Wilson    schedule 29.06.2012
comment
Это то, к чему я клоню - сначала проверка с учетом локали (NumberFormat может это сделать), затем Long.parseLong. Но теперь я не вижу, что было бы не так с проверкой типа возвращаемого значения.   -  person Marko Topolnik    schedule 29.06.2012
comment
@MarkoTopolnik Я думаю, что вопрос в заголовке был не самым лучшим. Я изменил его, чтобы указать, что я хочу обнаружить не обязательно переполнение, но когда возвращаемое значение не является точным представлением значения во входной строке.   -  person Garret Wilson    schedule 29.06.2012
comment
Double как никогда хорошо представляет собой точное представление входной строки, а Long всегда имеет идеальную точность.   -  person Marko Topolnik    schedule 29.06.2012
comment
@MarkTopolnik «Двойной» как никогда точное представление входной строки ... Это очень интересное утверждение. Конечно, все мы знаем, что существует множество дробных значений, которые не могут быть представлены числом с плавающей запятой. Верно ли ваше утверждение для целочисленных значений? Кажется, вы говорите, что если я приведу long к double, а затем обратно к long, в большинстве случаев я не получу того же значения, что меня очень удивит. Если бы вы говорили о дробных значениях, я бы согласился, но это не относится к делу.   -  person Garret Wilson    schedule 29.06.2012
comment
1.1111222223333344E24 Это не равно 1111122222333334444455555 что вы подразумеваете под этим? Вы хотите, чтобы они были равны как строка или как число? пример: 2+3 и 5 не равны как строка, но как число они одинаковы. только представление другое.   -  person Mohammad Adil    schedule 29.06.2012
comment
@MohammadAdil Пожалуйста, перепроверьте ваше преобразование научной нотации --- в первом значении отсутствует группа 5.   -  person Garret Wilson    schedule 29.06.2012
comment
@GarretWilson я скопировал ваш вопрос :)   -  person Mohammad Adil    schedule 29.06.2012
comment
@MohammadAdil Да, я знаю — эти два значения разные, в этом и суть вопроса.   -  person Garret Wilson    schedule 29.06.2012
comment
Теперь это, вероятно, ясно, но все же большую часть времени я не получаю одно и то же значение: это действительно так, и чем дальше от нуля, тем точнее утверждение. В крайних точках диапазона long плотность составляет примерно один к двум тысячам (один long из двух тысяч можно представить, округлив некоторое значение double).   -  person Marko Topolnik    schedule 29.06.2012


Ответы (3)


Отношение javadocs для parse() указывает, что он вернет Long, если это возможно, в противном случае он вернет Double. Поэтому просто убедитесь, что возвращаемое значение является длинным.

«Возвращает Long, если это возможно (например, в пределах диапазона [Long.MIN_VALUE, Long.MAX_VALUE] и без десятичных знаков), в противном случае — Double».

«Как я могу узнать, точно ли значение Double, которое я возвращаю из NumberFormat, эквивалентно целочисленному значению, представленному в исходной строке?»

Если он возвращает Double, то он не точно эквивалентен вашему интегральному значению, потому что Double не может точно представлять значения такой величины. Конкретный пример:

  Number a = numberFormat.parse("-9223372036854775809"); // Integer.MIN_VALUE - 1
  Number b = numberFormat.parse("-9223372036854775810"); // Integer.MIN_VALUE - 2
  System.out.println((a.equals(b))); // prints "true"
  Number c = numberFormat.parse("-9223372036854776800");
  System.out.println((a.equals(c))); // prints "true"
person Enwired    schedule 29.06.2012
comment
Мой заголовок был недостаточно ясен. Что я действительно хочу определить, так это точно ли возвращаемое значение отражает значение в строке. Поэтому, если возвращается double, который содержит значение Long.MIN_VALUE + 1, это нормально, если это то, что было в строке. Но как я узнаю, что возвращаемое значение соответствует тому, что действительно было в строке? - person Garret Wilson; 29.06.2012
comment
@GarretWilson Примите это во внимание: long охватывает все возможные целые числа со знаком, которые могут быть точно представлены с помощью 64 бит. Когда long переполняется, уже будет много, много целых чисел после последней точки, где последовательные значения double все еще могут покрывать последовательные целые числа (то есть могут точно представлять эти целые числа после округления). - person Marko Topolnik; 29.06.2012
comment
@MarkoTopolnik Это интересное наблюдение, но я не уверен, как оно отвечает на вопрос: как узнать, точно ли значение double, которое я получаю из NumberFormat, эквивалентно интегральному значению, представленному в исходной строке? Я говорю не только о переполнении; извините за путаницу --- я отредактировал заголовок. - person Garret Wilson; 29.06.2012
comment
В яблочко. Длинное.MIN_VALUE - 1 = -9223372036854775809. Невозможно точно представить это как Long или Double. NumberFormat.parse() вернет значение Double, которое даст вам понять, что оно неточно представляет целое число. Для Long.MIN_VALUE + 1 он вернет Long, сообщая вам, что число в порядке как целочисленное значение. - person Enwired; 29.06.2012
comment
@Enwired О, так вы говорите, что double не может представлять никаких целочисленных значений за пределами диапазона long? - person Garret Wilson; 29.06.2012
comment
@GarretWilson Вы можете просто быть уверены, что если это Double, то оно не совсем эквивалентно какому-либо целому значению. Я думаю, что 0 - единственное такое целое число (это может быть неправильно, но в любом случае это побочный момент). Единственная надежда состоит в том, что он округляет до нужного вам целого числа, и даже этот тест не пройден для всех, кроме очень немногих целых чисел в диапазоне за пределами Long. - person Marko Topolnik; 29.06.2012
comment
Например, @GarretWilson Double может ~представлять~ 1e+45, но, поскольку он не может определить разницу между последовательными целыми числами по размеру, я бы сказал, что он не может ~точно~ представлять целочисленное значение этой величины. - person Enwired; 29.06.2012
comment
Я проверил, мантисса имеет длину 52 бита, поэтому в основном вы можете найти одно из каждых 2 ^ (64-52) целых чисел рядом с Long.MAX_VALUE. Это один из четырех тысяч. - person Marko Topolnik; 29.06.2012
comment
@MarkoTopolnik Спасибо за обсуждение, я включил в ответ дополнительную информацию, частично основанную на ваших комментариях. - person Enwired; 29.06.2012
comment
Я принимаю этот ответ, потому что он наиболее близкий, возможно, самый близкий из возможных. Этот ответ на самом деле отвечает на вопрос: как узнать, что значение Double, которое я возвращаю из NumberFormat, вероятно, не эквивалентно интегральному значению, представленному в исходной строке? Также благодаря вкладу @MarkoTopolnik кажется, что, хотя double может точно представлять значения long, за пределами этого диапазона он точно представляет только несколько целочисленных значений. Оказывается, вопрос о переполнении не так уж и отличался; возвращаемое значение NumberFormat API немного странное, ИМХО. - person Garret Wilson; 29.06.2012

К вам вопрос -

If NumberFormat gives me a parsed value that does not equal that stored in the input string, how do I detect that?

Вы можете использовать

    if(number.toString().equals(string))
      //Parsed correctly
   else
     //Invalid parse
person Sunil Chavan    schedule 29.06.2012
comment
хорошая идея, но это не зависит от локали. number.toString() не зависит от локали, но numberFormat.parse() зависит от локали, и ему нужна процедура, которая будет работать с учетом локали. - person Enwired; 29.06.2012
comment
Я думаю, что это не то, что ищет OP. - person Mohammad Adil; 29.06.2012

Это может быть не решение, но заслуживает внимания.

public static void main(String[] args) {
        String string = "1111122222333334444455555";
        Locale locale = Locale.getDefault();
        NumberFormat numberFormat = NumberFormat.getIntegerInstance(locale);
        numberFormat.setParseIntegerOnly(true);
        Number number = numberFormat.parse(string);
        BigDecimal b = new BigDecimal(number.toString());
        System.out.println(b.toBigInteger());

    }

Вывод этого кода: 1111122222333334400000000

Как вы можете видеть, это не равно числу в фактической строке, поэтому может произойти переполнение.

person Mohammad Adil    schedule 29.06.2012
comment
Я не понимаю, как этот код отвечает на вопрос. Можно ли создать метод, который вводит String и возвращает Number, и если результирующее число не совпадает со значением в исходной строке, он выдает исключение? Я не понимаю, как код, который вы дали, делает это. - person Garret Wilson; 29.06.2012
comment
Что я делаю в этом коде, так это преобразование 1.1111222223333344E24 (проанализировано) в bigInteger, чтобы мы могли сравнить его с исходным числом в строковой форме. - person Mohammad Adil; 29.06.2012
comment
Да, но вы обнаружите, что он не будет работать с входными представлениями, зависящими от локали, такими как 1234 или 1.234. - person Garret Wilson; 29.06.2012
comment
Ссылаясь на ответ Enwired, он сказал, что если он возвращает Double, то он не совсем эквивалентен вашему интегральному значению, потому что Double не может точно представлять значения с такой величиной - я показал то же самое на примере... - person Mohammad Adil; 29.06.2012
comment
Нет, вы показали, что одно значение double не равно входному целому значению в строковой форме --- но мы уже знали это, потому что это был вопрос! Более того, вы также утверждали, что можете определить, было ли выходное значение double в точности равно интегральному значению во входной строке, а ваш код этого не делает! Если вы утверждаете, что это так, перепишите метод, чтобы использовать сигнатуру public Number parseNumber(String string, Locale locale) throws IllegalStateException, где он выдает исключение, если проанализированное числовое значение не будет равно целочисленному значению в строке. - person Garret Wilson; 30.06.2012