From jython-checkins at python.org Tue Apr 8 06:50:34 2014 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 8 Apr 2014 06:50:34 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_for_icu4j_support=2E?= Message-ID: <3g2x563M5zz7LkR@mail.python.org> http://hg.python.org/jython/rev/45b3007473f9 changeset: 7202:45b3007473f9 user: Jim Baker date: Tue Apr 08 04:50:24 2014 +0000 summary: Fix for icu4j support. files: Lib/unicodedata.py | 19 ++++++++++++++----- build.xml | 8 +++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Lib/unicodedata.py b/Lib/unicodedata.py --- a/Lib/unicodedata.py +++ b/Lib/unicodedata.py @@ -1,9 +1,18 @@ import java.lang.Character -from com.ibm.icu.text import Normalizer -from com.ibm.icu.lang import UCharacter, UProperty -from com.ibm.icu.util import VersionInfo -from com.ibm.icu.lang.UCharacter import EastAsianWidth, DecompositionType -from com.ibm.icu.lang.UCharacterEnums import ECharacterCategory, ECharacterDirection +try: + # import from jarjar-ed version + from org.python.icu.text import Normalizer + from org.python.icu.lang import UCharacter, UProperty + from org.python.icu.util import VersionInfo + from org.python.icu.lang.UCharacter import EastAsianWidth, DecompositionType + from org.python.icu.lang.UCharacterEnums import ECharacterCategory, ECharacterDirection +except ImportError: + # development version of Jython, so use extlibs + from com.ibm.icu.text import Normalizer + from com.ibm.icu.lang import UCharacter, UProperty + from com.ibm.icu.util import VersionInfo + from com.ibm.icu.lang.UCharacter import EastAsianWidth, DecompositionType + from com.ibm.icu.lang.UCharacterEnums import ECharacterCategory, ECharacterDirection __all__ = ( diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -445,7 +445,7 @@ - + @@ -576,6 +576,8 @@ + + @@ -596,8 +598,8 @@ - - + + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 10 15:51:36 2014 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 10 Apr 2014 15:51:36 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Added_tag_v2=2E7b2_for_chan?= =?utf-8?q?geset_45b3007473f9?= Message-ID: <3g4P0S62Wvz7LjV@mail.python.org> http://hg.python.org/jython/rev/4d841a769977 changeset: 7203:4d841a769977 user: Frank Wierzbicki date: Thu Apr 10 13:51:24 2014 +0000 summary: Added tag v2.7b2 for changeset 45b3007473f9 files: .hgtags | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -68,3 +68,5 @@ 92b054125b28a17b2e4b64ea6ffa416555a52725 v2.7b2 92b054125b28a17b2e4b64ea6ffa416555a52725 v2.7b2 3fe8544d3d592372da99103fc3720a36b949e57b v2.7b2 +3fe8544d3d592372da99103fc3720a36b949e57b v2.7b2 +45b3007473f9606be234eb64a5c98ad8ed160a31 v2.7b2 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Apr 15 21:58:56 2014 From: jython-checkins at python.org (jim.baker) Date: Tue, 15 Apr 2014 21:58:56 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fixed_bugs_corresponding_to?= =?utf-8?q?_test=5Fthreading_skips_for_Jython=2C_as?= Message-ID: <3g7cw02wtLz7LjM@mail.python.org> http://hg.python.org/jython/rev/b236e2db7a80 changeset: 7204:b236e2db7a80 user: Jim Baker date: Tue Apr 15 13:58:53 2014 -0600 summary: Fixed bugs corresponding to test_threading skips for Jython, as well as bug 2125 (missing threading.Thread.daemon attribute). files: Lib/test/lock_tests.py | 23 +++----- Lib/test/test_threading.py | 19 +----- Lib/threading.py | 27 ++++++++- src/org/python/modules/_threading/Condition.java | 26 +++++++-- src/org/python/modules/_threading/Lock.java | 4 +- 5 files changed, 57 insertions(+), 42 deletions(-) diff --git a/Lib/test/lock_tests.py b/Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py +++ b/Lib/test/lock_tests.py @@ -149,7 +149,7 @@ Tests for non-recursive, weak locks (which can be acquired and released from different threads). """ - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") + @unittest.skipIf(support.is_jython, "Jython only supports recursive locks") def test_reacquire(self): # Lock needs to be released before re-acquiring. lock = self.locktype() @@ -169,6 +169,7 @@ _wait() self.assertEqual(len(phase), 2) + @unittest.skipIf(support.is_jython, "Java does not allow locks to be released from different threads") def test_different_thread(self): # Lock can be released from a different thread. lock = self.locktype() @@ -194,7 +195,6 @@ lock.release() lock.release() - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_release_unacquired(self): # Cannot release an unacquired lock lock = self.locktype() @@ -207,7 +207,6 @@ lock.release() self.assertRaises(RuntimeError, lock.release) - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_different_thread(self): # Cannot release from a different thread lock = self.locktype() @@ -271,7 +270,6 @@ self.assertEqual(results1, [True] * N) self.assertEqual(results2, [True] * N) - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_notify(self): evt = self.eventtype() self._check_notify(evt) @@ -280,7 +278,6 @@ evt.clear() self._check_notify(evt) - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_timeout(self): evt = self.eventtype() results1 = [] @@ -294,9 +291,10 @@ results2.append((r, t2 - t1)) Bunch(f, N).wait_for_finished() self.assertEqual(results1, [False] * N) + epsilon = 1e-5 # wait time is hard to test precisely, so keep low resolution for r, dt in results2: self.assertFalse(r) - self.assertTrue(dt >= 0.2, dt) + self.assertTrue(dt >= (0.2 - epsilon), dt) # The event is set results1 = [] results2 = [] @@ -312,7 +310,6 @@ Tests for condition variables. """ - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_acquire(self): cond = self.condtype() # Be default we have an RLock: the condition can be acquired multiple @@ -324,20 +321,18 @@ lock = threading.Lock() cond = self.condtype(lock) cond.acquire() - self.assertFalse(lock.acquire(False)) + self.assertTrue(lock.acquire(False)) # All locks in Jython are recursive! cond.release() self.assertTrue(lock.acquire(False)) - self.assertFalse(cond.acquire(False)) + self.assertTrue(cond.acquire(False)) # All locks in Jython are recursive! lock.release() with cond: - self.assertFalse(lock.acquire(False)) + self.assertTrue(lock.acquire(False)) # All locks in Jython are recursive! - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_unacquired_wait(self): cond = self.condtype() self.assertRaises(RuntimeError, cond.wait) - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_unacquired_notify(self): cond = self.condtype() self.assertRaises(RuntimeError, cond.notify) @@ -411,7 +406,6 @@ # A second time, to check internal state is still ok. self._check_notify(cond) - @unittest.skipIf(support.is_jython, "FIXME: not working properly on Jython") def test_timeout(self): cond = self.condtype() results = [] @@ -425,8 +419,9 @@ results.append(t2 - t1) Bunch(f, N).wait_for_finished() self.assertEqual(len(results), 5) + epsilon = 1e-5 # wait time is hard to test precisely, so keep low resolution for dt in results: - self.assertTrue(dt >= 0.2, dt) + self.assertTrue(dt >= (0.2 - epsilon), dt) class BaseSemaphoreTests(BaseTestCase): diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -102,7 +102,6 @@ print 'all tasks done' self.assertEqual(numrunning.get(), 0) - @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_ident_of_no_threading_threads(self): # The ident still must work for the main thread and dummy threads. self.assertFalse(threading.currentThread().ident is None) @@ -118,7 +117,6 @@ del threading._active[ident[0]] # run with a small(ish) thread stack size (256kB) - @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_various_ops_small_stack(self): if verbose: print 'with 256kB thread stack size...' @@ -132,7 +130,6 @@ threading.stack_size(0) # run with a large thread stack size (1MB) - @unittest.skipIf(is_jython, "FIXME: not working on Jython") def test_various_ops_large_stack(self): if verbose: print 'with 1MB thread stack size...' @@ -256,7 +253,7 @@ t.join() # else the thread is still running, and we have no way to kill it - @unittest.skipIf(is_jython, "FIXME: not working properly on Jython") + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_limbo_cleanup(self): # Issue 7481: Failure to start thread should cleanup the limbo map. def fail_new_thread(*args): @@ -272,7 +269,7 @@ finally: threading._start_new_thread = _start_new_thread - @unittest.skipIf(is_jython, "FIXME: investigate on Jython") + @unittest.skipIf(is_jython, "Does not apply to Jython") def test_finalize_runnning_thread(self): # Issue 1402: the PyGILState_Ensure / _Release functions may be called # very late on python exit: on deallocation of a running thread for @@ -311,7 +308,6 @@ """]) self.assertEqual(rc, 42) - @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_finalize_with_trace(self): # Issue1733757 # Avoid a deadlock when sys.settrace steps into threading._shutdown @@ -346,7 +342,6 @@ self.assertTrue(rc == 0, "Unexpected error: " + repr(stderr)) - @unittest.skipIf(is_jython, "FIXME: investigate on Jython") def test_join_nondaemon_on_shutdown(self): # Issue 1722344 # Raising SystemExit skipped threading._shutdown @@ -368,8 +363,8 @@ self.addCleanup(p.stdout.close) self.addCleanup(p.stderr.close) stdout, stderr = p.communicate() - self.assertEqual(stdout.strip(), - "Woke up, sleep function is: ") + self.assertTrue(stdout.strip().startswith( + "Woke up, sleep function is: = 0, "Semaphore initial value must be >= 0" + if value < 0: + raise ValueError("Semaphore initial value must be >= 0") _Verbose.__init__(self, verbose) self.__cond = Condition(Lock()) self.__value = value diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -87,11 +87,15 @@ @ExposedMethod(defaults = "Py.None") final void Condition_wait(PyObject timeout) throws InterruptedException { - if (timeout == Py.None) { - _condition.await(); - } else { - long nanos = (long) (timeout.asDouble() * 1e9); - _condition.awaitNanos(nanos); + try { + if (timeout == Py.None) { + _condition.await(); + } else { + long nanos = (long) (timeout.asDouble() * 1e9); + _condition.awaitNanos(nanos); + } + } catch (IllegalMonitorStateException ex) { + throw Py.RuntimeError("cannot wait on un-acquired lock"); } } @@ -101,7 +105,11 @@ @ExposedMethod final void Condition_notify() { - _condition.signal(); + try { + _condition.signal(); + } catch (IllegalMonitorStateException ex) { + throw Py.RuntimeError("cannot notify on un-acquired lock"); + } } public void notifyAll$() { @@ -110,7 +118,11 @@ @ExposedMethod final void Condition_notifyAll() { - _condition.signalAll(); + try { + _condition.signalAll(); + } catch (IllegalMonitorStateException ex) { + throw Py.RuntimeError("cannot notify on un-acquired lock"); + } } @ExposedMethod diff --git a/src/org/python/modules/_threading/Lock.java b/src/org/python/modules/_threading/Lock.java --- a/src/org/python/modules/_threading/Lock.java +++ b/src/org/python/modules/_threading/Lock.java @@ -61,8 +61,8 @@ @ExposedMethod final void Lock_release() { - if (!_lock.isHeldByCurrentThread()) { - throw Py.AssertionError("release() of un-acquire()d lock"); + if (!_lock.isHeldByCurrentThread() || _lock.getHoldCount() <= 0) { + throw Py.RuntimeError("cannot release un-acquired lock"); } _lock.unlock(); } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Apr 16 21:15:21 2014 From: jython-checkins at python.org (jim.baker) Date: Wed, 16 Apr 2014 21:15:21 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fixes_relative_star_import_?= =?utf-8?q?support_=28bug_1973=29=2E_Also_modifies_regrtest?= Message-ID: <3g8CvF67NNz7LkZ@mail.python.org> http://hg.python.org/jython/rev/83a4f55711aa changeset: 7205:83a4f55711aa user: Jim Baker date: Wed Apr 16 13:15:14 2014 -0600 summary: Fixes relative star import support (bug 1973). Also modifies regrtest such that more tests are run, including test_import. files: .idea/copyright/profiles_settings.xml | 4 +- Lib/test/regrtest.py | 60 ++++------ Lib/test/test_support.py | 34 ++++- src/org/python/compiler/CodeCompiler.java | 3 - src/org/python/core/imp.java | 14 ++- 5 files changed, 65 insertions(+), 50 deletions(-) diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,5 +1,3 @@ - - - + \ No newline at end of file diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -1180,6 +1180,7 @@ """, 'java': """ + # Not supportable on Java, or at least requires additional emulation in Jython test__locale test__rawffi test_aepack @@ -1194,20 +1195,12 @@ test_capi test_cd test_cl - test_commands - test_compileall - test_crypt test_ctypes - test_curses - test_dbm test_dl - test_email_codecs - test_epoll test_fcntl test_fork1 test_gdb test_gdbm - test_grp test_getargs2 test_gl test_hotshot @@ -1217,39 +1210,28 @@ test_kqueue test_largefile test_linuxaudiodev - test_locale - test_longexp test_macfs test_macostools - test_mhlib test_mmap test_modulefinder test_msilib test_multiprocessing test_nis - test_normalization test_openpty test_ossaudiodev test_parser test_plistlib - test_poll - test_profile test_pty test_resource test_rgbimg test_scriptpackages - test_socket_ssl - test_socketserver test_sqlite - test_ssl - test_startfile test_strop test_structmembers test_sunaudiodev test_sundry test_symtable test_tcl - test_timeout test_tk test_tools test_ttk_guionly @@ -1262,25 +1244,36 @@ test_winsound test_zipfile64 - test_gzip + # Could rewrite these tests + test_epoll + test_poll + test_profile + + # Causing issues + test_distutils + test_email_codecs + test_io + + # Should fix these tests so they are not hardcoded for CPython pyc files + # test_compileall + # test_pydoc + + # Requires Python bytecode compilation support + test_longexp + + # Tests that should work with socket-reboot, but currently hang test_ftplib + test_httplib test_poplib - test_pydoc - test_queue test_smtplib + test_socket_ssl + test_socketserver test_telnetlib + test_timeout - test_binascii - test_distutils - test_dumbdbm - test_pbcvm - test_readline - test_shutil - test_sys_setprofile - test_sys_settrace - test_urllib2_localnet + test_sys_setprofile # revisit for GC + test_sys_settrace # revisit for line jumping """ - #Last group above should be re-evaluated before releasing 2.7. } _expectations['freebsd5'] = _expectations['freebsd4'] _expectations['freebsd6'] = _expectations['freebsd4'] @@ -1305,9 +1298,8 @@ test_dis test_dummy_threading test_eof - test_frozen + test_frozen # not meaningful for Jython, although it is similar to Clamp singlejar test_gc - test_import test_iterlen test_multibytecodec test_multibytecodec_support diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -631,28 +631,46 @@ else: testcase.fail('Missing SyntaxError: "%s"' % statement) -def open_urlresource(url): +def open_urlresource(url, check=None): import urlparse, urllib2 - requires('urlfetch') filename = urlparse.urlparse(url)[2].split('/')[-1] # '/': it's URL! - for path in [os.path.curdir, os.path.pardir]: - fn = os.path.join(path, filename) - if os.path.exists(fn): - return open(fn) + fn = os.path.join(os.path.dirname(__file__), "data", filename) + + def check_valid_file(fn): + f = open(fn) + if check is None: + return f + elif check(f): + f.seek(0) + return f + f.close() + + if os.path.exists(fn): + f = check_valid_file(fn) + if f is not None: + return f + unlink(fn) + + # Verify the requirement before downloading the file + requires('urlfetch') print >> get_original_stdout(), '\tfetching %s ...' % url f = urllib2.urlopen(url, timeout=15) try: - with open(filename, "wb") as out: + with open(fn, "wb") as out: s = f.read() while s: out.write(s) s = f.read() finally: f.close() - return open(filename) + + f = check_valid_file(fn) + if f is not None: + return f + raise TestFailed('invalid resource "%s"' % fn) class WarningsRecorder(object): diff --git a/src/org/python/compiler/CodeCompiler.java b/src/org/python/compiler/CodeCompiler.java --- a/src/org/python/compiler/CodeCompiler.java +++ b/src/org/python/compiler/CodeCompiler.java @@ -974,9 +974,6 @@ if (aliases == null || aliases.size() == 0) { throw new ParseException("Internel parser error", node); } else if (aliases.size() == 1 && aliases.get(0).getInternalName().equals("*")) { - if (node.getInternalLevel() > 0) { - throw new ParseException("'import *' not allowed with 'from .'", node); - } if (my_scope.func_level > 0) { module.error("import * only allowed at module level", false, node); diff --git a/src/org/python/core/imp.java b/src/org/python/core/imp.java --- a/src/org/python/core/imp.java +++ b/src/org/python/core/imp.java @@ -822,8 +822,18 @@ */ private static PyObject import_module_level(String name, boolean top, PyObject modDict, PyObject fromlist, int level) { - if (name.length() == 0 && level <= 0) { - throw Py.ValueError("Empty module name"); + if (name.length() == 0) { + if (level == 0 || modDict == null) { + throw Py.ValueError("Empty module name"); + } else { + PyObject moduleName = modDict.__findattr__("__name__"); + if (moduleName != null && moduleName.toString().equals("__name__")) { + throw Py.ValueError("Attempted relative import in non-package"); + } + } + } + if (name.indexOf(File.separatorChar) != -1) { + throw Py.ImportError("Import by filename is not supported."); } PyObject modules = Py.getSystemState().modules; PyObject pkgMod = null; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 17 01:24:58 2014 From: jython-checkins at python.org (jim.baker) Date: Thu, 17 Apr 2014 01:24:58 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Make_SRE=5FSTATE_cache_the_?= =?utf-8?q?code_points_for_a_given_PyString_=28pull_request_=2327?= Message-ID: <3g8KRG1HWfz7Ljd@mail.python.org> http://hg.python.org/jython/rev/dcec75209b7d changeset: 7206:dcec75209b7d user: Indra Talip date: Wed Apr 16 17:24:09 2014 -0600 summary: Make SRE_STATE cache the code points for a given PyString (pull request #27 against bitbucket) files: registry | 17 +++ src/org/python/core/Options.java | 10 ++ src/org/python/modules/sre/SRE_STATE.java | 49 ++++++++++- 3 files changed, 75 insertions(+), 1 deletions(-) diff --git a/registry b/registry --- a/registry +++ b/registry @@ -80,3 +80,20 @@ # functionality by having an entry such as os:com.foo.jni.os #python.modules.builtin = whatever +# This registry entry controls the behaviour of the SRE_STATE code point cache. +# For the complete set of values that can be set here see: +# http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilderSpec.html +# Typically you will want to set weakKeys so as to perform object identity +# comparisons rather than using equals(). Using weakKeys also enables entries +# to be removed when the underlying key is garbage collected. +# +# Values that could be useful to tweak are: +# - concurrencyLevel: this is a hint to the cache as to the level of partitioning to +# use for the intenal map and affects the number of concurrent *write* operations +# - expireAfterAccess: constrains the size of the cache and the order +# in which cache entries are evicted. The current value is chosen somewhat +# arbitrarily so tweak as required. +# - maximumWeight: weighting is based on the length of the int[] returned from +# PyString.toCodePoints(). As such this setting contrains the amount of memory +# that the cache will consume. The current value is 10MB. +#python.sre.cachespec = weakKeys,concurrencyLevel=4,maximumWeight=2621440,expireAfterAccess=30s diff --git a/src/org/python/core/Options.java b/src/org/python/core/Options.java --- a/src/org/python/core/Options.java +++ b/src/org/python/core/Options.java @@ -109,6 +109,14 @@ */ public static int division_warning = 0; + /** + * Cache spec for the SRE_STATE code point cache. The value maps to the + * CacheBuilderSpec string and affects how the SRE_STATE cache will behave/evict + * cached PyString -> int[] code points. + */ + public static final String sreCacheSpecDefault = "weakKeys,concurrencyLevel=4,maximumWeight=2621440,expireAfterAccess=30s"; + public static String sreCacheSpec = sreCacheSpecDefault; + // // ####### END OF OPTIONS // @@ -191,5 +199,7 @@ + "setting: '" + prop + "'"); } } + + Options.sreCacheSpec = getStringOption("sre.cachespec", Options.sreCacheSpec); } } diff --git a/src/org/python/modules/sre/SRE_STATE.java b/src/org/python/modules/sre/SRE_STATE.java --- a/src/org/python/modules/sre/SRE_STATE.java +++ b/src/org/python/modules/sre/SRE_STATE.java @@ -16,6 +16,12 @@ // Last updated to _sre.c: 2.52 package org.python.modules.sre; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.Weigher; +import org.python.core.Options; +import org.python.core.Py; import org.python.core.PyString; public class SRE_STATE { @@ -1245,8 +1251,49 @@ /* duplicated from the PatternObject */ int flags; + private enum CACHE { + INSTANCE(Options.sreCacheSpec); + private LoadingCache cache; + + private CACHE(String spec) { + CacheLoader loader = new CacheLoader() { + @Override + public int[] load(PyString key) { + return key.toCodePoints(); + } + }; + + CacheBuilder builder; + try { + builder = CacheBuilder.from(spec); + } catch (IllegalArgumentException iae) { + Py.writeWarning("re", + String.format("Incompatible options in python.sre.cachespec '%s' due to: %s", + new Object[] {spec, iae.getMessage()})); + Py.writeMessage("re", String.format("Defaulting python.sre.cachespec to '%s'", + Options.sreCacheSpecDefault)); + builder = CacheBuilder.from(Options.sreCacheSpecDefault); + } + + if (spec.contains("maximumWeight")) { + cache = builder.weigher(new Weigher() { + @Override + public int weigh(PyString k, int[] v) { + return v.length; + } + }).build(loader); + } else { + cache = builder.build(loader); + } + } + + private int[] get(PyString str) { + return cache.getUnchecked(str); + } + } + public SRE_STATE(PyString str, int start, int end, int flags) { - this.str = str.toCodePoints(); + this.str = CACHE.INSTANCE.get(str); int size = str.__len__(); this.charsize = 1; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 17 02:28:36 2014 From: jython-checkins at python.org (jim.baker) Date: Thu, 17 Apr 2014 02:28:36 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_regression_tests_for_te?= =?utf-8?q?st=5Fglob_=28pull_request_=2329_against_bitbucket=29?= Message-ID: <3g8Lrh397bz7Ljd@mail.python.org> http://hg.python.org/jython/rev/7096b5a3f8e7 changeset: 7207:7096b5a3f8e7 user: Indra Talip date: Wed Apr 16 18:28:27 2014 -0600 summary: Fix regression tests for test_glob (pull request #29 against bitbucket) files: src/org/python/modules/posix/PosixModule.java | 39 +++++++-- 1 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/org/python/modules/posix/PosixModule.java b/src/org/python/modules/posix/PosixModule.java --- a/src/org/python/modules/posix/PosixModule.java +++ b/src/org/python/modules/posix/PosixModule.java @@ -13,6 +13,9 @@ import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; import java.security.SecureRandom; import java.util.Iterator; import java.util.Map; @@ -727,17 +730,23 @@ public static void unlink(PyObject path) { String absolutePath = absolutePath(path); - File file = new File(absolutePath); - if (file.isDirectory()) { - throw Py.OSError(Errno.EISDIR, path); - } else if (!file.delete()) { - // Something went wrong, does stat raise an error? - posix.stat(absolutePath); - // It exists, do we not have permissions? - if (!file.canWrite()) { - throw Py.OSError(Errno.EPERM, path); + try { + Path nioPath = new File(absolutePath).toPath(); + if (Files.isDirectory(nioPath, LinkOption.NOFOLLOW_LINKS)) { + throw Py.OSError(Errno.EISDIR, path); + } else if (!Files.deleteIfExists(nioPath)) { + // Something went wrong, does stat raise an error? + posix.stat(absolutePath); + // It exists, do we not have permissions? + if (!Files.isWritable(nioPath)) { + throw Py.OSError(Errno.EPERM, path); + } + throw Py.OSError("unlink(): an unknown error occurred: " + absolutePath); } - throw Py.OSError("unlink(): an unknown error occurred: " + absolutePath); + } catch (IOException ex) { + PyException pyError = Py.OSError("unlink(): an unknown error occurred: " + absolutePath); + pyError.initCause(ex); + throw pyError; } } @@ -935,7 +944,15 @@ @Override public PyObject __call__(PyObject path) { - return PyStatResult.fromFileStat(posix.lstat(absolutePath(path))); + String absolutePath = absolutePath(path); + + // round tripping from a string to a file to a string loses + // trailing slashes so add them back back in to get correct posix.lstat + // behaviour if path is not a directory. + if (asPath(path).endsWith(File.separator)) { + absolutePath = absolutePath + File.separator; + } + return PyStatResult.fromFileStat(posix.lstat(absolutePath)); } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 17 02:30:20 2014 From: jython-checkins at python.org (jim.baker) Date: Thu, 17 Apr 2014 02:30:20 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_For_now=2C_do_not_run_test?= =?utf-8?q?=5Flocale_-_causes_issues_on_Linux_in_subsequent_tests?= Message-ID: <3g8Lth0KP2z7Ljd@mail.python.org> http://hg.python.org/jython/rev/035eded55c4d changeset: 7208:035eded55c4d user: Jim Baker date: Wed Apr 16 18:30:13 2014 -0600 summary: For now, do not run test_locale - causes issues on Linux in subsequent tests files: Lib/test/regrtest.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -1249,10 +1249,11 @@ test_poll test_profile - # Causing issues + # The following tests cause issues for tests that are subsequently run test_distutils test_email_codecs test_io + test_locale # Should fix these tests so they are not hardcoded for CPython pyc files # test_compileall -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Apr 22 23:02:52 2014 From: jython-checkins at python.org (jim.baker) Date: Tue, 22 Apr 2014 23:02:52 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Upgrade_Jar_Jar_Links_to_1?= =?utf-8?q?=2E4_to_fix_bug_2129?= Message-ID: <3gCy0X2Jf8z7LjV@mail.python.org> http://hg.python.org/jython/rev/a5bc0032cf79 changeset: 7209:a5bc0032cf79 user: Jim Baker date: Tue Apr 22 15:02:43 2014 -0600 summary: Upgrade Jar Jar Links to 1.4 to fix bug 2129 files: build.xml | 4 ++-- extlibs/jarjar-0.7.jar | Bin extlibs/jarjar-1.4.jar | Bin 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -565,7 +565,7 @@ - + @@ -628,7 +628,7 @@ - + diff --git a/extlibs/jarjar-0.7.jar b/extlibs/jarjar-0.7.jar deleted file mode 100644 index 7f1c4871f386981bbcd8b1dc3166b12920c80f4a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch [stripped] diff --git a/extlibs/jarjar-1.4.jar b/extlibs/jarjar-1.4.jar new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..68b9db9aa5049a8519af8ea6fdaafe0399e4da59 GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Apr 23 05:46:16 2014 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 23 Apr 2014 05:46:16 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_javadoc_pointer=2E?= Message-ID: <3gD6y00xS6z7LjV@mail.python.org> http://hg.python.org/jython/rev/4105892103e8 changeset: 7210:4105892103e8 user: Frank Wierzbicki date: Wed Apr 23 03:46:06 2014 +0000 summary: Fix javadoc pointer. files: build.xml | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -701,7 +701,7 @@ windowtitle="Jython API documentation" bottom="<a href='http://www.jython.org' target='_top'>Jython homepage</a>" > - + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Apr 23 05:46:55 2014 From: jython-checkins at python.org (frank.wierzbicki) Date: Wed, 23 Apr 2014 05:46:55 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Added_tag_v2=2E7b2_for_chan?= =?utf-8?q?geset_4105892103e8?= Message-ID: <3gD6yl4W2Kz7LjV@mail.python.org> http://hg.python.org/jython/rev/e9c44964857e changeset: 7211:e9c44964857e user: Frank Wierzbicki date: Wed Apr 23 03:46:39 2014 +0000 summary: Added tag v2.7b2 for changeset 4105892103e8 files: .hgtags | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -70,3 +70,5 @@ 3fe8544d3d592372da99103fc3720a36b949e57b v2.7b2 3fe8544d3d592372da99103fc3720a36b949e57b v2.7b2 45b3007473f9606be234eb64a5c98ad8ed160a31 v2.7b2 +45b3007473f9606be234eb64a5c98ad8ed160a31 v2.7b2 +4105892103e8db1bfb539757a399ce9b79a899e0 v2.7b2 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Apr 23 21:08:46 2014 From: jython-checkins at python.org (jim.baker) Date: Wed, 23 Apr 2014 21:08:46 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fixed_encodings=2Eidna_so_t?= =?utf-8?q?hat_it_uses_jar_jar_links_version_of_icu_if_available?= Message-ID: <3gDWQQ4Y6Mz7LjP@mail.python.org> http://hg.python.org/jython/rev/02fd15a0075b changeset: 7212:02fd15a0075b user: Jim Baker date: Wed Apr 23 13:08:38 2014 -0600 summary: Fixed encodings.idna so that it uses jar jar links version of icu if available files: Lib/encodings/idna.py | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-) diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py --- a/Lib/encodings/idna.py +++ b/Lib/encodings/idna.py @@ -1,7 +1,12 @@ import codecs import re -from com.ibm.icu.text import StringPrep, StringPrepParseException from java.net import IDN +try: + # import from jarjar-ed version if available + from org.python.icu.text import StringPrep, StringPrepParseException +except ImportError: + # dev version of Jython, so use extlibs + from com.ibm.icu.text import StringPrep, StringPrepParseException # IDNA section 3.1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:04 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:04 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Float_formatting_-_comment_?= =?utf-8?q?changes_only_=28and_insignificant_code_chage=29=2E?= Message-ID: <3gDbJh1xZlz7Ljk@mail.python.org> http://hg.python.org/jython/rev/67fd5ccc094e changeset: 7213:67fd5ccc094e parent: 7197:68e2098b8f12 user: Jeff Allen date: Thu Apr 03 08:39:42 2014 +0100 summary: Float formatting - comment changes only (and insignificant code chage). Added comments to clarify the contract and logic of core.stringlib.Formatter and related classes ahead of improvements. Largely automatic formatting changes to code (import list, indents, trailing space, universal curly brackets, etc.). files: src/org/python/core/PyString.java | 69 +- src/org/python/core/stringlib/Formatter.java | 301 +++++++-- src/org/python/core/stringlib/InternalFormatSpec.java | 58 +- src/org/python/core/stringlib/InternalFormatSpecParser.java | 29 +- 4 files changed, 375 insertions(+), 82 deletions(-) diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java --- a/src/org/python/core/PyString.java +++ b/src/org/python/core/PyString.java @@ -3766,6 +3766,16 @@ } } + /** + * Implements PEP-3101 {}-formatting methods str.format() and + * unicode.format(). + * + * @param value the format string + * @param args to be interpolated into the string + * @param keywords for the trailing args + * @param enclosingIterator when used nested + * @return the formatted string based on the arguments + */ protected String buildFormattedString(String value, PyObject[] args, String[] keywords, MarkupIterator enclosingIterator) { StringBuilder result = new StringBuilder(); @@ -3775,12 +3785,20 @@ if (chunk == null) { break; } + // A Chunk encapsulates a literal part ... result.append(chunk.literalText); + // ... and the parsed form of the replacement field that followed it (if any) if (chunk.fieldName.length() > 0) { + // The grammar of the replacement field is: + // "{" [field_name] ["!" conversion] [":" format_spec] "}" + + // Get the object referred to by the field name (which may be omitted). PyObject fieldObj = getFieldObject(chunk.fieldName, args, keywords); if (fieldObj == null) { continue; } + + // The conversion specifier is s = __str__ or r = __repr__. if ("r".equals(chunk.conversion)) { fieldObj = fieldObj.__repr__(); } else if ("s".equals(chunk.conversion)) { @@ -3788,12 +3806,15 @@ } else if (chunk.conversion != null) { throw Py.ValueError("Unknown conversion specifier " + chunk.conversion); } + + // The format_spec may be simple, or contained nested replacement fields. String formatSpec = chunk.formatSpec; if (chunk.formatSpecNeedsExpanding) { if (enclosingIterator != null) { // PEP 3101 says only 2 levels throw Py.ValueError("Max string recursion exceeded"); } + // Recursively interpolate further args into chunk.formatSpec formatSpec = buildFormattedString(formatSpec, args, keywords, it); } renderField(fieldObj, formatSpec, result); @@ -3802,6 +3823,15 @@ return result.toString(); } + /** + * Return the object referenced by a given field name, interpreted in the context of the given + * argument list, containing positional and keyword arguments. + * + * @param fieldName to interpret. + * @param args argument list (positional then keyword arguments). + * @param keywords naming the keyword arguments. + * @return the object designated or null. + */ private PyObject getFieldObject(String fieldName, PyObject[] args, String[] keywords) { FieldNameIterator iterator = new FieldNameIterator(fieldName); Object head = iterator.head(); @@ -3814,6 +3844,7 @@ throw Py.IndexError("tuple index out of range"); } obj = args[index]; + } else { for (int i = 0; i < keywords.length; i++) { if (keywords[i].equals(head)) { @@ -3825,6 +3856,7 @@ throw Py.KeyError((String)head); } } + if (obj != null) { while (true) { FieldNameIterator.Chunk chunk = iterator.nextChunk(); @@ -3844,9 +3876,18 @@ } } } + return obj; } + /** + * Append to a formatting result, the presentation of one object, according to a given format + * specification and the object's __format__ method. + * + * @param fieldObj to format. + * @param formatSpec specification to apply. + * @param result to which the result will be appended. + */ private void renderField(PyObject fieldObj, String formatSpec, StringBuilder result) { PyString formatSpecStr = formatSpec == null ? Py.EmptyString : new PyString(formatSpec); result.append(fieldObj.__format__(formatSpecStr).asString()); @@ -3876,10 +3917,12 @@ } /** - * Internal implementation of str.__format__() + * Format the given text according to a parsed PEP 3101 formatting specification, as during + * text.__format__(format_spec) or "{:s}".format(text) where + * text is a Python string. * - * @param text the text to format - * @param spec the PEP 3101 formatting specification + * @param text to format + * @param spec the parsed PEP 3101 formatting specification * @return the result of the formatting */ public static String formatString(String text, InternalFormatSpec spec) { @@ -3954,6 +3997,9 @@ } +/** + * Interpreter for %-format strings. (Note visible across the core package.) + */ final class StringFormatter { int index; @@ -3985,6 +4031,12 @@ this(format, false); } + /** + * Initialise the interpreter with the given format string, ready for {@link #format(PyObject)}. + * + * @param format string to interpret + * @param unicodeCoercion to indicate a PyUnicode result is expected + */ public StringFormatter(String format, boolean unicodeCoercion) { index = 0; this.format = format; @@ -3995,16 +4047,11 @@ PyObject getarg() { PyObject ret = null; switch (argIndex) { - // special index indicating a mapping - case -3: + case -3: // special index indicating a mapping return args; - // special index indicating a single item that has already been - // used - case -2: + case -2: // special index indicating a single item that has already been used break; - // special index indicating a single item that has not yet been - // used - case -1: + case -1: // special index indicating a single item that has not yet been used argIndex = -2; return args; default: diff --git a/src/org/python/core/stringlib/Formatter.java b/src/org/python/core/stringlib/Formatter.java --- a/src/org/python/core/stringlib/Formatter.java +++ b/src/org/python/core/stringlib/Formatter.java @@ -1,20 +1,44 @@ package org.python.core.stringlib; -import org.python.core.*; -import org.python.core.util.ExtraMath; -import java.math.BigInteger; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import org.python.core.Py; +import org.python.core.util.ExtraMath; +/** + * This class provides an approximate equivalent to corresponding parts of CPython's + * "~/Objects/stringlib/formatter.h", by concentrating in one place the formatting capabilities of + * built-in numeric types float and complex. + */ public class Formatter { + /** + * Format a floating-point value according to a conversion specification (created by + * {@link InternalFormatSpecParser#parse()}), the type of which must be one of + * {efgEFG%}, including padding to width. + * + * @param value to convert + * @param spec for a floating-point conversion + * @return formatted result + */ public static String formatFloat(double value, InternalFormatSpec spec) { InternalFormatter f = new InternalFormatter(spec); String string = f.format(value); return spec.pad(string, '>', 0); } + /** + * Format a complex value according to a conversion specification (created by + * {@link InternalFormatSpecParser#parse()}), the type of which must be one of + * {efgEFG}, including padding to width. The operation is effectively the + * application of the floating-point format to the real an imaginary parts, then the addition of + * padding once. + * + * @param value to convert + * @param spec for a floating-point conversion + * @return formatted result + */ public static String formatComplex(double real, double imag, InternalFormatSpec spec) { String string; InternalFormatter f = new InternalFormatter(spec); @@ -29,44 +53,76 @@ } } -//Adapted from PyString's StringFormatter class. + +/** + * A class that provides the implementation of floating-point formatting, and holds a conversion + * specification (created by {@link InternalFormatSpecParser#parse()}), a derived precision, and the + * sign of the number being converted. + */ +// Adapted from PyString's StringFormatter class. final class InternalFormatter { + InternalFormatSpec spec; boolean negative; int precision; + /** + * Construct the formatter from a specification: default missing {@link #precision} to 6. + * + * @param spec parsed conversion specification + */ public InternalFormatter(InternalFormatSpec spec) { this.spec = spec; this.precision = spec.precision; - if (this.precision == -1) + if (this.precision == -1) { this.precision = 6; + } } + /** + * If {@link #precision} exceeds an implementation limit, raise {@link Py#OverflowError}. + * + * @param type to name as the type being formatted + */ private void checkPrecision(String type) { - if(precision > 250) { + if (precision > 250) { // A magic number. Larger than in CPython. throw Py.OverflowError("formatted " + type + " is too long (precision too long?)"); } - + } - private String formatExp(long v, int radix) { + /** + * Format abs(e) (in the given radix) with zero-padding to 2 decimal places, and + * store sgn(e) in {@link #negative}. + * + * @param e to convert + * @param radix in which to express + * @return string value of abs(e) base radix. + */ + private String formatExp(long e, int radix) { checkPrecision("integer"); - if (v < 0) { + if (e < 0) { negative = true; - v = -v; + e = -e; } - String s = Long.toString(v, radix); + String s = Long.toString(e, radix); while (s.length() < 2) { - s = "0"+s; + s = "0" + s; } return s; } + /** + * Holds in its {@link #template} member, a {@link DecimalFormat} initialised for fixed point + * float formatting. + */ static class DecimalFormatTemplate { + static DecimalFormat template; static { - template = new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US)); + template = + new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US)); DecimalFormatSymbols symbols = template.getDecimalFormatSymbols(); symbols.setNaN("nan"); symbols.setInfinity("inf"); @@ -75,14 +131,26 @@ } } + /** + * Return a copy of the pre-configured {@link DecimalFormatTemplate#template}, which may be + * further customised by the client. + * + * @return the template + */ private static final DecimalFormat getDecimalFormat() { return (DecimalFormat)DecimalFormatTemplate.template.clone(); } + /** + * Holds in its {@link #template} member, a {@link DecimalFormat} initialised for fixed point + * float formatting with percentage scaling and furniture. + */ static class PercentageFormatTemplate { + static DecimalFormat template; static { - template = new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US)); + template = + new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US)); DecimalFormatSymbols symbols = template.getDecimalFormatSymbols(); symbols.setNaN("nan"); symbols.setInfinity("inf"); @@ -91,35 +159,72 @@ } } + /** + * Return a copy of the pre-configured {@link PercentageFormatTemplate#template}, which may be + * further customised by the client. + * + * @return the template + */ private static final DecimalFormat getPercentageFormat() { return (DecimalFormat)PercentageFormatTemplate.template.clone(); } + /** + * Format abs(v) in '{f}' format to {@link #precision} (places after + * decimal point), and store sgn(v) in {@link #negative}. Truncation is provided + * for that will remove trailing zeros and the decimal point (e.g. 1.200 becomes + * 1.2, and 4.000 becomes 4. This treatment is to support + * '{g}' format. (Also potentially '%g' format.) Truncation is not + * used (cannot validly be specified) for '{f}' format. + * + * @param v to convert + * @param truncate if true strip trailing zeros and decimal point + * @return converted value + */ private String formatFloatDecimal(double v, boolean truncate) { + checkPrecision("decimal"); + + // Separate the sign from v if (v < 0) { v = -v; negative = true; } + // Configure a DecimalFormat: express truncation via minimumFractionDigits DecimalFormat decimalFormat = getDecimalFormat(); decimalFormat.setMaximumFractionDigits(precision); decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision); + // The DecimalFormat is already configured to group by comma at group size 3. if (spec.thousands_separators) { decimalFormat.setGroupingUsed(true); } + String ret = decimalFormat.format(v); return ret; } + /** + * Format 100*abs(v) to {@link #precision} (places after decimal point), with a '%' + * (percent) sign following, and store sgn(v) in {@link #negative}. + * + * @param v to convert + * @param truncate if true strip trailing zeros + * @return converted value + */ private String formatPercentage(double v, boolean truncate) { + checkPrecision("decimal"); + + // Separate the sign from v if (v < 0) { v = -v; negative = true; } + // Configure a DecimalFormat: express truncation via minimumFractionDigits + // XXX but truncation cannot be specified with % format! DecimalFormat decimalFormat = getPercentageFormat(); decimalFormat.setMaximumFractionDigits(precision); decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision); @@ -128,26 +233,53 @@ return ret; } + /** + * Format abs(v) in '{e}' format to {@link #precision} (places after + * decimal point), and store sgn(v) in {@link #negative}. Truncation is provided + * for that will remove trailing zeros and the decimal point before the exponential part (e.g. + * 1.200e+04 becomes 1.2e+04, and 4.000e+05 becomes + * 4e+05. This treatment is to support '{g}' format. (Also potentially + * '%g' format.) Truncation is not used (cannot validly be specified) for + * '{e}' format. + * + * @param v to convert + * @param truncate if true strip trailing zeros and decimal point + * @return converted value + */ private String formatFloatExponential(double v, char e, boolean truncate) { - StringBuilder buf = new StringBuilder(); + + // Separate the sign from v boolean isNegative = false; if (v < 0) { v = -v; isNegative = true; } + + /* + * Separate treatment is given to the exponent (I think) because java.text.DecimalFormat + * will insert a sign in a positive exponent, as in 1.234e+45 where Java writes 1.234E45. + */ + + // Power of 10 that will be the exponent. double power = 0.0; - if (v > 0) + if (v > 0) { + // That is, if not zero (or NaN) power = ExtraMath.closeFloor(Math.log10(v)); + } + + // Get exponent (as text) String exp = formatExp((long)power, 10); if (negative) { + // This is the sign of the power-of-ten *exponent* negative = false; - exp = '-'+exp; - } - else { + exp = '-' + exp; + } else { exp = '+' + exp; } - double base = v/Math.pow(10, power); + // Format the mantissa as a fixed point number + double base = v / Math.pow(10, power); + StringBuilder buf = new StringBuilder(); buf.append(formatFloatDecimal(base, truncate)); buf.append(e); @@ -157,22 +289,40 @@ return buf.toString(); } + /** + * Format a floating-point number according to the specification represented by this + * InternalFormatter. The conversion type, precision, and flags for grouping or + * percentage are dealt with here. At the point this is used, we know the {@link #spec} has type + * in {efgEFG}. + * + * @param value to convert + * @return formatted version + */ @SuppressWarnings("fallthrough") public String format(double value) { - String string; + + // XXX Possible duplication in handling NaN and upper/lower case here when methiods + // floatFormatDecimal, formatFloatExponential, etc. appear to do those things. + + String string; // return value if (spec.alternate) { + // XXX in %g, but not {:g} alternate form means always include a decimal point throw Py.ValueError("Alternate form (#) not allowed in float format specifier"); } + int sign = Double.compare(value, 0.0d); if (Double.isNaN(value)) { + // Express NaN cased according to the conversion type. if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') { string = "NAN"; } else { string = "nan"; } + } else if (Double.isInfinite(value)) { + // Express signed infinity cased according to the conversion type. if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') { if (value > 0) { string = "INF"; @@ -186,52 +336,75 @@ string = "-inf"; } } + } else { - switch(spec.type) { - case 'e': - case 'E': - string = formatFloatExponential(value, spec.type, false); - if (spec.type == 'E') { - string = string.toUpperCase(); - } - break; - case 'f': - case 'F': - string = formatFloatDecimal(value, false); - if (spec.type == 'F') { - string = string.toUpperCase(); - } - break; - case 'g': - case 'G': - int exponent = (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value))); - int origPrecision = precision; - if (exponent >= -4 && exponent < precision) { - precision -= exponent + 1; - string = formatFloatDecimal(value, !spec.alternate); - } else { - // Exponential precision is the number of digits after the decimal - // point, whereas 'g' precision is the number of significant digits -- - // and exponential always provides one significant digit before the - // decimal point - precision--; - string = formatFloatExponential(value, (char)(spec.type-2), !spec.alternate); - } - if (spec.type == 'G') { - string = string.toUpperCase(); - } - precision = origPrecision; - break; - case '%': - string = formatPercentage(value, false); - break; - default: - //Should never get here, since this was checked in PyFloat. - throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'", - spec.type)); + switch (spec.type) { + case 'e': + case 'E': + // Exponential case: 1.23e-45 + string = formatFloatExponential(value, spec.type, false); + if (spec.type == 'E') { + string = string.toUpperCase(); + } + break; + + case 'f': + case 'F': + // Fixed case: 123.45 + string = formatFloatDecimal(value, false); + if (spec.type == 'F') { + string = string.toUpperCase(); + } + break; + + case 'g': + case 'G': + // Mixed "general" case: e or f format according to exponent. + // XXX technique not wholly effective, for example on 0.0000999999999999995. + int exponent = + (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value))); + int origPrecision = precision; + /* + * (Python docs) Suppose formatting with presentation type 'e' and precision p-1 + * would give exponent exp. Then if -4 <= exp < p, ... + */ + if (exponent >= -4 && exponent < precision) { + /* + * ... the number is formatted with presentation type 'f' and precision + * p-1-exp. + */ + precision -= exponent + 1; + string = formatFloatDecimal(value, !spec.alternate); + } else { + /* + * ... Otherwise, the number is formatted with presentation type 'e' and + * precision p-1. + */ + precision--; + string = + formatFloatExponential(value, (char)(spec.type - 2), + !spec.alternate); + } + if (spec.type == 'G') { + string = string.toUpperCase(); + } + precision = origPrecision; + break; + + case '%': + // Multiplies by 100 and displays in f-format, followed by a percent sign. + string = formatPercentage(value, false); + break; + + default: + // Should never get here, since this was checked in PyFloat. + throw Py.ValueError(String.format( + "Unknown format code '%c' for object of type 'float'", spec.type)); } } + + // If positive, deal with mandatory sign, or mandatory space. if (sign >= 0) { if (spec.sign == '+') { string = "+" + string; @@ -239,6 +412,8 @@ string = " " + string; } } + + // If negative, insert a minus sign where needed, and we haven't already (e.g. "-inf"). if (sign < 0 && string.charAt(0) != '-') { string = "-" + string; } diff --git a/src/org/python/core/stringlib/InternalFormatSpec.java b/src/org/python/core/stringlib/InternalFormatSpec.java --- a/src/org/python/core/stringlib/InternalFormatSpec.java +++ b/src/org/python/core/stringlib/InternalFormatSpec.java @@ -1,42 +1,88 @@ package org.python.core.stringlib; /** - * Parsed PEP-3101 format specification of a single field. + * Parsed PEP-3101 format specification of a single field. This class holds the several attributes + * that might be decoded from a format specifier. It provides a method + * {@link #pad(String, char, int)} for adjusting a string using those attributes related to padding + * to a string assumed to be the result of formatting to the given precision. + *

+ * This structure is returned by {@link InternalFormatSpecParser#parse()} and having public members + * is freely used by {@link InternalFormatSpecParser}, {@link Formatter} and the __format__ methods + * of client object types. + *

+ * The fields correspond to the elements of a format specification. The grammar of a format + * specification is: + * + *

+ * [[fill]align][sign][#][0][width][,][.precision][type]
+ * 
*/ public final class InternalFormatSpec { + + /** The fill specified in the grammar. */ public char fill_char; + /** Alignment indicator is 0, or one of {'<', '^', '>', '=' . */ public char align; + /** The alternative format flag '#' was given. */ public boolean alternate; + /** Sign-handling flag, one of '+', '-', or ' '. */ public char sign; + /** Width to which to pad the resault in {@link #pad(String, char, int)}. */ public int width = -1; + /** Insert the grouping separator (which in Python always indicates a group-size of 3). */ public boolean thousands_separators; + /** Precision decoded from the format. */ public int precision = -1; + /** Type key from the format. */ public char type; + /** + * Pad value, using {@link #fill_char} (or ' ') before and after, to {@link #width} + * -leaveWidth, aligned according to {@link #align} (or according to + * defaultAlign). + * + * @param value to pad + * @param defaultAlign to use if this.align=0 (one of '<', + * '^', '>', or '='). + * @param leaveWidth to reduce effective this.width by + * @return + */ public String pad(String value, char defaultAlign, int leaveWidth) { + + // We'll need this many pad characters (if>0) int remaining = width - value.length() - leaveWidth; if (remaining <= 0) { return value; } - StringBuilder result = new StringBuilder(); - int leading = remaining; + + // Use this.align or defaultAlign int useAlign = align; if (useAlign == 0) { useAlign = defaultAlign; } + + // By default all padding is leading padding ('<' case or '=') + int leading = remaining; if (useAlign == '^') { - leading = remaining/2; + // Half the padding before + leading = remaining / 2; } else if (useAlign == '<') { + // All the padding after leading = 0; } + + // Now build the result + StringBuilder result = new StringBuilder(); char fill = fill_char != 0 ? fill_char : ' '; - for (int i = 0; i < leading; i++) { + + for (int i = 0; i < leading; i++) { // before result.append(fill); } result.append(value); - for (int i = 0; i < remaining - leading; i++) { + for (int i = 0; i < remaining - leading; i++) { // after result.append(fill); } + return result.toString(); } } diff --git a/src/org/python/core/stringlib/InternalFormatSpecParser.java b/src/org/python/core/stringlib/InternalFormatSpecParser.java --- a/src/org/python/core/stringlib/InternalFormatSpecParser.java +++ b/src/org/python/core/stringlib/InternalFormatSpecParser.java @@ -1,19 +1,26 @@ package org.python.core.stringlib; /** - * Parser for PEP-3101 field format specifications. + * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} method + * that translates the format specification into an InternalFormatSpec object. */ public class InternalFormatSpecParser { + private String spec; private int index; + /** + * Constructor simply holds the specification streang ahead of the {@link #parse()} operation. + * + * @param spec format specifier to parse (e.g. "<+12.3f") + */ public InternalFormatSpecParser(String spec) { this.spec = spec; this.index = 0; } private static boolean isAlign(char c) { - switch(c) { + switch (c) { case '<': case '>': case '=': @@ -24,6 +31,24 @@ } } + /** + * Parse the specification with which this object was initialised into an + * {@link InternalFormatSpec}, which is an object encapsulating the format for use by formatting + * methods. This parser deals only with the format specifiers themselves, as accepted by the + * __format__ method of a type, or the format() built-in, not format + * strings in general as accepted by str.format(). A typical idiom is: + * + *
+     * InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
+     * 
+ * + * @return the InternalFormatSpec equivalent to the constructor argument + */ + /* + * This method is the equivalent of CPython's parse_internal_render_format_spec() in + * ~/Objects/stringlib/formatter.h. + */ + // XXX Better encapsulated as a constructor of InternalFormatSpec? public InternalFormatSpec parse() { InternalFormatSpec result = new InternalFormatSpec(); if (spec.length() >= 1 && isAlign(spec.charAt(0))) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:06 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:06 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Formatting_changes_only=2E?= Message-ID: <3gDbJk0BdNz7Lk7@mail.python.org> http://hg.python.org/jython/rev/56db40fe0a8b changeset: 7214:56db40fe0a8b user: Jeff Allen date: Thu Apr 17 13:35:02 2014 +0100 summary: Formatting changes only. Reformatting, curly brackets, etc. in modules ahead of substantive change. files: src/org/python/core/PyComplex.java | 99 +++----- src/org/python/core/PyFloat.java | 190 ++++++++-------- 2 files changed, 133 insertions(+), 156 deletions(-) diff --git a/src/org/python/core/PyComplex.java b/src/org/python/core/PyComplex.java --- a/src/org/python/core/PyComplex.java +++ b/src/org/python/core/PyComplex.java @@ -1,13 +1,10 @@ -/* - * Copyright (c) Corporation for National Research Initiatives - * Copyright (c) Jython Developers - */ +// Copyright (c) Corporation for National Research Initiatives +// Copyright (c) Jython Developers package org.python.core; import org.python.core.stringlib.Formatter; import org.python.core.stringlib.InternalFormatSpec; import org.python.core.stringlib.InternalFormatSpecParser; - import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; @@ -46,7 +43,7 @@ @ExposedNew public static PyObject complex_new(PyNewWrapper new_, boolean init, PyType subtype, - PyObject[] args, String[] keywords) { + PyObject[] args, String[] keywords) { ArgParser ap = new ArgParser("complex", args, keywords, "real", "imag"); PyObject real = ap.getPyObject(0, Py.Zero); PyObject imag = ap.getPyObject(1, null); @@ -54,14 +51,12 @@ // Special-case for single argument that is already complex if (real.getType() == TYPE && new_.for_type == subtype && imag == null) { return real; - } - if (real instanceof PyString) { + } else if (real instanceof PyString) { if (imag != null) { throw Py.TypeError("complex() can't take second arg if first is a string"); } return real.__complex__(); - } - if (imag != null && imag instanceof PyString) { + } else if (imag != null && imag instanceof PyString) { throw Py.TypeError("complex() second arg can't be a string"); } @@ -79,7 +74,7 @@ PyComplex complexImag; PyFloat toFloat = null; if (real instanceof PyComplex) { - complexReal = (PyComplex) real; + complexReal = (PyComplex)real; } else { try { toFloat = real.__float__(); @@ -96,7 +91,7 @@ if (imag == null) { complexImag = new PyComplex(0.0); } else if (imag instanceof PyComplex) { - complexImag = (PyComplex) imag; + complexImag = (PyComplex)imag; } else { toFloat = null; try { @@ -129,7 +124,7 @@ public static String toString(double value) { if (value == Math.floor(value) && value <= Long.MAX_VALUE && value >= Long.MIN_VALUE) { - return Long.toString((long) value); + return Long.toString((long)value); } else { return Double.toString(value); } @@ -164,7 +159,7 @@ return new PyFloat(real).hashCode(); } else { long v = Double.doubleToLongBits(real) ^ Double.doubleToLongBits(imag); - return (int) v ^ (int) (v >> 32); + return (int)v ^ (int)(v >> 32); } } @@ -282,21 +277,18 @@ } /** - * Coercion logic for complex. Implemented as a final method to avoid - * invocation of virtual methods from the exposed coerce. + * Coercion logic for complex. Implemented as a final method to avoid invocation of virtual + * methods from the exposed coerce. */ final PyObject complex___coerce_ex__(PyObject other) { if (other instanceof PyComplex) { return other; - } - if (other instanceof PyFloat) { - return new PyComplex(((PyFloat) other).getValue(), 0); - } - if (other instanceof PyInteger) { - return new PyComplex(((PyInteger) other).getValue(), 0); - } - if (other instanceof PyLong) { - return new PyComplex(((PyLong) other).doubleValue(), 0); + } else if (other instanceof PyFloat) { + return new PyComplex(((PyFloat)other).getValue(), 0); + } else if (other instanceof PyInteger) { + return new PyComplex(((PyInteger)other).getValue(), 0); + } else if (other instanceof PyLong) { + return new PyComplex(((PyLong)other).doubleValue(), 0); } return Py.None; } @@ -308,16 +300,13 @@ private final PyComplex coerce(PyObject other) { if (other instanceof PyComplex) { - return (PyComplex) other; - } - if (other instanceof PyFloat) { - return new PyComplex(((PyFloat) other).getValue(), 0); - } - if (other instanceof PyInteger) { - return new PyComplex(((PyInteger) other).getValue(), 0); - } - if (other instanceof PyLong) { - return new PyComplex(((PyLong) other).doubleValue(), 0); + return (PyComplex)other; + } else if (other instanceof PyFloat) { + return new PyComplex(((PyFloat)other).getValue(), 0); + } else if (other instanceof PyInteger) { + return new PyComplex(((PyInteger)other).getValue(), 0); + } else if (other instanceof PyLong) { + return new PyComplex(((PyLong)other).doubleValue(), 0); } throw Py.TypeError("xxx"); } @@ -377,8 +366,8 @@ } private final static PyObject _mul(PyComplex o1, PyComplex o2) { - return new PyComplex(o1.real * o2.real - o1.imag * o2.imag, - o1.real * o2.imag + o1.imag * o2.real); + return new PyComplex(o1.real * o2.real - o1.imag * o2.imag, // + o1.real * o2.imag + o1.imag * o2.real); } @Override @@ -417,14 +406,14 @@ } double ratio = b.imag / b.real; double denom = b.real + b.imag * ratio; - return new PyComplex((a.real + a.imag * ratio) / denom, - (a.imag - a.real * ratio) / denom); + return new PyComplex((a.real + a.imag * ratio) / denom, // + (a.imag - a.real * ratio) / denom); } else { /* divide tops and bottom by b.imag */ double ratio = b.real / b.imag; double denom = b.real * ratio + b.imag; - return new PyComplex((a.real * ratio + a.imag) / denom, - (a.imag * ratio - a.real) / denom); + return new PyComplex((a.real * ratio + a.imag) / denom, // + (a.imag * ratio - a.real) / denom); } } @@ -437,8 +426,7 @@ final PyObject complex___div__(PyObject right) { if (!canCoerce(right)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic complex division"); } return _div(this, coerce(right)); @@ -453,8 +441,7 @@ final PyObject complex___rdiv__(PyObject left) { if (!canCoerce(left)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic complex division"); } return _div(coerce(left), this); @@ -550,7 +537,7 @@ private static PyObject _mod(PyComplex value, PyComplex right) { Py.warning(Py.DeprecationWarning, "complex divmod(), // and % are deprecated"); - PyComplex z = (PyComplex) _div(value, right); + PyComplex z = (PyComplex)_div(value, right); z.real = Math.floor(z.real); z.imag = 0.0; @@ -586,7 +573,7 @@ private static PyObject _divmod(PyComplex value, PyComplex right) { Py.warning(Py.DeprecationWarning, "complex divmod(), // and % are deprecated"); - PyComplex z = (PyComplex) _div(value, right); + PyComplex z = (PyComplex)_div(value, right); z.real = Math.floor(z.real); z.imag = 0.0; @@ -636,12 +623,12 @@ return complex___pow__(right, modulo); } - @ExposedMethod(type = MethodType.BINARY, defaults = "null", doc = BuiltinDocs.complex___pow___doc) + @ExposedMethod(type = MethodType.BINARY, defaults = "null", + doc = BuiltinDocs.complex___pow___doc) final PyObject complex___pow__(PyObject right, PyObject modulo) { if (modulo != null) { throw Py.ValueError("complex modulo"); - } - if (!canCoerce(right)) { + } else if (!canCoerce(right)) { return null; } return _pow(this, coerce(right)); @@ -677,7 +664,7 @@ } // Check for integral powers - int iexp = (int) yr; + int iexp = (int)yr; if (yi == 0 && yr == iexp && iexp >= -128 && iexp <= 128) { return ipow(value, iexp); } @@ -764,6 +751,7 @@ return new PyComplex(real, imag); } + @Override public PyComplex conjugate() { return complex_conjugate(); } @@ -798,13 +786,13 @@ throw Py.TypeError("__format__ requires str or unicode"); } - PyString formatSpecStr = (PyString) formatSpec; + PyString formatSpecStr = (PyString)formatSpec; String result; try { String specString = formatSpecStr.getString(); InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse(); switch (spec.type) { - case '\0': /* No format code: like 'g', but with at least one decimal. */ + case '\0': // No format code: like 'g', but with at least one decimal. case 'e': case 'E': case 'f': @@ -817,8 +805,8 @@ break; default: /* unknown */ - throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'complex'", - spec.type)); + throw Py.ValueError(String.format( + "Unknown format code '%c' for object of type 'complex'", spec.type)); } } catch (IllegalArgumentException e) { throw Py.ValueError(e.getMessage()); @@ -826,7 +814,6 @@ return formatSpecStr.createInstance(result); } - @Override public boolean isNumberType() { return true; diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java --- a/src/org/python/core/PyFloat.java +++ b/src/org/python/core/PyFloat.java @@ -1,23 +1,20 @@ -/* - * Copyright (c) Corporation for National Research Initiatives - * Copyright (c) Jython Developers - */ +// Copyright (c) Corporation for National Research Initiatives +// Copyright (c) Jython Developers package org.python.core; +import java.io.Serializable; +import java.math.BigDecimal; + import org.python.core.stringlib.Formatter; import org.python.core.stringlib.InternalFormatSpec; import org.python.core.stringlib.InternalFormatSpecParser; -import org.python.modules.math; -import java.io.Serializable; -import java.math.BigDecimal; -import java.math.RoundingMode; - import org.python.expose.ExposedClassMethod; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; import org.python.expose.ExposedType; import org.python.expose.MethodType; +import org.python.modules.math; /** * A builtin python float. @@ -47,12 +44,12 @@ } public PyFloat(float v) { - this((double) v); + this((double)v); } @ExposedNew public static PyObject float_new(PyNewWrapper new_, boolean init, PyType subtype, - PyObject[] args, String[] keywords) { + PyObject[] args, String[] keywords) { ArgParser ap = new ArgParser("float", args, keywords, new String[] {"x"}, 0); PyObject x = ap.getPyObject(0, null); if (x == null) { @@ -69,9 +66,9 @@ if (e.match(Py.AttributeError)) { // Translate AttributeError to TypeError // XXX: We are using the same message as CPython, even if - // it is not strictly correct (instances of types - // that implement the __float__ method are also - // valid arguments) + // it is not strictly correct (instances of types + // that implement the __float__ method are also + // valid arguments) throw Py.TypeError("float() argument must be a string or a number"); } throw e; @@ -96,9 +93,9 @@ @ExposedClassMethod(doc = BuiltinDocs.float_fromhex_doc) public static PyObject float_fromhex(PyType type, PyObject o) { - //XXX: I'm sure this could be shortened/simplified, but Double.parseDouble() takes - // non-hex strings and requires the form 0xNUMBERpNUMBER for hex input which - // causes extra complexity here. + // XXX: I'm sure this could be shortened/simplified, but Double.parseDouble() takes + // non-hex strings and requires the form 0xNUMBERpNUMBER for hex input which + // causes extra complexity here. String message = "invalid hexadecimal floating-point string"; boolean negative = false; @@ -108,19 +105,16 @@ if (value.length() == 0) { throw Py.ValueError(message); - } - if (value.equals("nan") || value.equals("-nan") || value.equals("+nan")) { + } else if (value.equals("nan") || value.equals("-nan") || value.equals("+nan")) { return new PyFloat(Double.NaN); - } - if (value.equals("inf") ||value.equals("infinity") || - value.equals("+inf") ||value.equals("+infinity")) { + } else if (value.equals("inf") || value.equals("infinity") || value.equals("+inf") + || value.equals("+infinity")) { return new PyFloat(Double.POSITIVE_INFINITY); - } - if (value.equals("-inf") || value.equals("-infinity")) { + } else if (value.equals("-inf") || value.equals("-infinity")) { return new PyFloat(Double.NEGATIVE_INFINITY); } - //Strip and record + or - + // Strip and record + or - if (value.charAt(0) == '-') { value = value.substring(1); negative = true; @@ -131,17 +125,17 @@ throw Py.ValueError(message); } - //Append 0x if not present. + // Append 0x if not present. if (!value.startsWith("0x") && !value.startsWith("0X")) { value = "0x" + value; } - //reattach - if needed. + // reattach - if needed. if (negative) { value = "-" + value; } - //Append p if not present. + // Append p if not present. if (value.indexOf('p') == -1) { value = value + "p0"; } @@ -159,7 +153,7 @@ // @ExposedClassMethod(doc = BuiltinDocs.float_hex_doc) // public static PyObject float_hex(PyType type, double value) { - // return new PyString(Double.toHexString(value)); + // return new PyString(Double.toHexString(value)); // } private String pyHexString(Double f) { @@ -167,25 +161,31 @@ // the most efficient, but we don't expect this to be a hot // spot in our code either String java_hex = Double.toHexString(getValue()); - if (java_hex.equals("Infinity")) return "inf"; - if (java_hex.equals("-Infinity")) return "-inf"; - if (java_hex.equals("NaN")) return "nan"; - if (java_hex.equals("0x0.0p0")) return "0x0.0p+0"; - if (java_hex.equals("-0x0.0p0")) return "-0x0.0p+0"; + if (java_hex.equals("Infinity")) { + return "inf"; + } else if (java_hex.equals("-Infinity")) { + return "-inf"; + } else if (java_hex.equals("NaN")) { + return "nan"; + } else if (java_hex.equals("0x0.0p0")) { + return "0x0.0p+0"; + } else if (java_hex.equals("-0x0.0p0")) { + return "-0x0.0p+0"; + } - // replace hex rep of MpE to conform with Python such that - // 1. M is padded to 16 digits (ignoring a leading -) - // 2. Mp+E if E>=0 + // replace hex rep of MpE to conform with Python such that + // 1. M is padded to 16 digits (ignoring a leading -) + // 2. Mp+E if E>=0 // example: result of 42.0.hex() is translated from - // 0x1.5p5 to 0x1.5000000000000p+5 + // 0x1.5p5 to 0x1.5000000000000p+5 int len = java_hex.length(); boolean start_exponent = false; StringBuilder py_hex = new StringBuilder(len + 1); int padding = f > 0 ? 17 : 18; - for (int i=0; i < len; i++) { + for (int i = 0; i < len; i++) { char c = java_hex.charAt(i); if (c == 'p') { - for (int pad=i; pad < padding; pad++) { + for (int pad = i; pad < padding; pad++) { py_hex.append('0'); } start_exponent = true; @@ -194,9 +194,9 @@ py_hex.append('+'); } start_exponent = false; - } + } py_hex.append(c); - } + } return py_hex.toString(); } @@ -274,23 +274,22 @@ double value = getValue(); if (Double.isInfinite(value)) { return value < 0 ? -271828 : 314159; - } - if (Double.isNaN(value)) { + } else if (Double.isNaN(value)) { return 0; } - + double intPart = Math.floor(value); double fractPart = value - intPart; if (fractPart == 0) { if (intPart <= Integer.MAX_VALUE && intPart >= Integer.MIN_VALUE) { - return (int) value; + return (int)value; } else { return __long__().hashCode(); } } else { long v = Double.doubleToLongBits(getValue()); - return (int) v ^ (int) (v >> 32); + return (int)v ^ (int)(v >> 32); } } @@ -307,10 +306,9 @@ @Override public Object __tojava__(Class c) { if (c == Double.TYPE || c == Number.class || c == Double.class || c == Object.class - || c == Serializable.class) { + || c == Serializable.class) { return new Double(getValue()); - } - if (c == Float.TYPE || c == Float.class) { + } else if (c == Float.TYPE || c == Float.class) { return new Float(getValue()); } return super.__tojava__(c); @@ -345,7 +343,7 @@ @Override public PyObject __ge__(PyObject other) { - //NaN >= anything is always false. + // NaN >= anything is always false. if (Double.isNaN(getValue())) { return Py.False; } @@ -354,7 +352,7 @@ @Override public PyObject __lt__(PyObject other) { - //NaN < anything is always false. + // NaN < anything is always false. if (Double.isNaN(getValue())) { return Py.False; } @@ -363,7 +361,7 @@ @Override public PyObject __le__(PyObject other) { - //NaN >= anything is always false. + // NaN >= anything is always false. if (Double.isNaN(getValue())) { return Py.False; } @@ -382,7 +380,7 @@ double j; if (other instanceof PyFloat) { - j = ((PyFloat) other).getValue(); + j = ((PyFloat)other).getValue(); } else if (!isFinite()) { // we're infinity: our magnitude exceeds any finite // integer, so it doesn't matter which int we compare i @@ -393,10 +391,10 @@ return -2; } } else if (other instanceof PyInteger) { - j = ((PyInteger) other).getValue(); + j = ((PyInteger)other).getValue(); } else if (other instanceof PyLong) { BigDecimal v = new BigDecimal(getValue()); - BigDecimal w = new BigDecimal(((PyLong) other).getValue()); + BigDecimal w = new BigDecimal(((PyLong)other).getValue()); return v.compareTo(w); } else { return -2; @@ -425,21 +423,18 @@ } /** - * Coercion logic for float. Implemented as a final method to avoid - * invocation of virtual methods from the exposed coerce. + * Coercion logic for float. Implemented as a final method to avoid invocation of virtual + * methods from the exposed coerce. */ final Object float___coerce_ex__(PyObject other) { if (other instanceof PyFloat) { return other; + } else if (other instanceof PyInteger) { + return new PyFloat((double)((PyInteger)other).getValue()); + } else if (other instanceof PyLong) { + return new PyFloat(((PyLong)other).doubleValue()); } else { - if (other instanceof PyInteger) { - return new PyFloat((double) ((PyInteger) other).getValue()); - } - if (other instanceof PyLong) { - return new PyFloat(((PyLong) other).doubleValue()); - } else { - return Py.None; - } + return Py.None; } } @@ -449,11 +444,11 @@ private static double coerce(PyObject other) { if (other instanceof PyFloat) { - return ((PyFloat) other).getValue(); + return ((PyFloat)other).getValue(); } else if (other instanceof PyInteger) { - return ((PyInteger) other).getValue(); + return ((PyInteger)other).getValue(); } else if (other instanceof PyLong) { - return ((PyLong) other).doubleValue(); + return ((PyLong)other).doubleValue(); } else { throw Py.TypeError("xxx"); } @@ -544,8 +539,7 @@ final PyObject float___div__(PyObject right) { if (!canCoerce(right)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic float division"); } @@ -565,8 +559,7 @@ final PyObject float___rdiv__(PyObject left) { if (!canCoerce(left)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic float division"); } @@ -667,7 +660,7 @@ return null; } double rightv = coerce(right); - return new PyFloat(modulo(getValue(),rightv)); + return new PyFloat(modulo(getValue(), rightv)); } @Override @@ -729,18 +722,16 @@ return float___pow__(right, modulo); } - @ExposedMethod(type = MethodType.BINARY, defaults = "null", - doc = BuiltinDocs.float___pow___doc) + @ExposedMethod(type = MethodType.BINARY, defaults = "null", // + doc = BuiltinDocs.float___pow___doc) final PyObject float___pow__(PyObject right, PyObject modulo) { if (!canCoerce(right)) { return null; - } - - if (modulo != null) { + } else if (modulo != null) { throw Py.TypeError("pow() 3rd argument not allowed unless all arguments are integers"); } - return _pow( getValue(), coerce(right), modulo); + return _pow(getValue(), coerce(right), modulo); } @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.float___rpow___doc) @@ -769,8 +760,7 @@ if (value == 0.0) { if (iw < 0.0) { throw Py.ZeroDivisionError("0.0 cannot be raised to a negative power"); - } - if (Double.isNaN(iw)) { + } else if (Double.isNaN(iw)) { return new PyFloat(Double.NaN); } return new PyFloat(0); @@ -838,7 +828,7 @@ @ExposedMethod(doc = BuiltinDocs.float___int___doc) final PyObject float___int__() { if (getValue() <= Integer.MAX_VALUE && getValue() >= Integer.MIN_VALUE) { - return new PyInteger((int) getValue()); + return new PyInteger((int)getValue()); } return __long__(); } @@ -902,7 +892,7 @@ @ExposedMethod(doc = BuiltinDocs.float_is_integer_doc) final boolean float_is_integer() { if (Double.isInfinite(value)) { - return false; + return false; } return Math.floor(value) == value; } @@ -937,16 +927,16 @@ throw Py.TypeError("__format__ requires str or unicode"); } - PyString formatSpecStr = (PyString) formatSpec; + PyString formatSpecStr = (PyString)formatSpec; String result; try { String specString = formatSpecStr.getString(); InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse(); - if (spec.type == '\0'){ + if (spec.type == '\0') { return (Py.newFloat(d)).__str__(); } switch (spec.type) { - case '\0': /* No format code: like 'g', but with at least one decimal. */ + case '\0': // No format code: like 'g', but with at least one decimal. case 'e': case 'E': case 'f': @@ -959,8 +949,8 @@ break; default: /* unknown */ - throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'", - spec.type)); + throw Py.ValueError(String.format( + "Unknown format code '%c' for object of type 'float'", spec.type)); } } catch (IllegalArgumentException e) { throw Py.ValueError(e.getMessage()); @@ -979,14 +969,15 @@ PyTuple frexp = math.frexp(value); double float_part = ((Double)frexp.get(0)).doubleValue(); int exponent = ((Integer)frexp.get(1)).intValue(); - for (int i=0; i<300 && float_part != Math.floor(float_part); i++) { + for (int i = 0; i < 300 && float_part != Math.floor(float_part); i++) { float_part *= 2.0; exponent--; } - /* self == float_part * 2**exponent exactly and float_part is integral. - If FLT_RADIX != 2, the 300 steps may leave a tiny fractional part - to be truncated by PyLong_FromDouble(). */ - + /* + * self == float_part * 2**exponent exactly and float_part is integral. If FLT_RADIX != 2, + * the 300 steps may leave a tiny fractional part to be truncated by PyLong_FromDouble(). + */ + PyLong numerator = new PyLong(float_part); PyLong denominator = new PyLong(1); PyLong py_exponent = new PyLong(Math.abs(exponent)); @@ -1013,9 +1004,7 @@ // but this is what Python demands public enum Format { - UNKNOWN("unknown"), - BE("IEEE, big-endian"), - LE("IEEE, little-endian"); + UNKNOWN("unknown"), BE("IEEE, big-endian"), LE("IEEE, little-endian"); private final String format; @@ -1027,6 +1016,7 @@ return format; } } + // subset of IEEE-754, the JVM is big-endian public static volatile Format double_format = Format.BE; public static volatile Format float_format = Format.BE; @@ -1050,14 +1040,14 @@ } if (Format.LE.format().equals(format)) { throw Py.ValueError(String.format("can only set %s format to 'unknown' or the " - + "detected platform value", typestr)); + + "detected platform value", typestr)); } else if (Format.BE.format().equals(format)) { new_format = Format.BE; } else if (Format.UNKNOWN.format().equals(format)) { new_format = Format.UNKNOWN; } else { - throw Py.ValueError("__setformat__() argument 2 must be 'unknown', " + - "'IEEE, little-endian' or 'IEEE, big-endian'"); + throw Py.ValueError("__setformat__() argument 2 must be 'unknown', " + + "'IEEE, little-endian' or 'IEEE, big-endian'"); } if (new_format != null) { if ("double".equals(typestr)) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:07 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:07 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Formatting_changes_only_=28?= =?utf-8?q?PyInteger=29=2E?= Message-ID: <3gDbJl2rgVz7Ljx@mail.python.org> http://hg.python.org/jython/rev/e66679906050 changeset: 7215:e66679906050 user: Jeff Allen date: Thu Apr 17 23:59:53 2014 +0100 summary: Formatting changes only (PyInteger). Reformatting, curly brackets, etc. in modules ahead of substantive change. files: src/org/python/core/PyInteger.java | 93 ++++++++--------- 1 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/org/python/core/PyInteger.java b/src/org/python/core/PyInteger.java --- a/src/org/python/core/PyInteger.java +++ b/src/org/python/core/PyInteger.java @@ -1,7 +1,5 @@ -/* - * Copyright (c) Corporation for National Research Initiatives - * Copyright (c) Jython Developers - */ +// Copyright (c) Corporation for National Research Initiatives +// Copyright (c) Jython Developers package org.python.core; import java.io.Serializable; @@ -112,9 +110,9 @@ * Convert all sorts of object types to either PyInteger or PyLong, * using their {@link PyObject#__int__()} method, whether exposed or not, or if that raises an * exception (as the base PyObject one does), using any __trunc__() - * the type may have exposed. If all this fails, this method raises an exception. Equivalent to CPython - * PyNumber_Int(). - * + * the type may have exposed. If all this fails, this method raises an exception. Equivalent to + * CPython PyNumber_Int(). + * * @param x to convert to an int * @return int or long result. * @throws PyException (TypeError) if no method of conversion can be found @@ -151,7 +149,7 @@ /** * Helper called on whatever exposed method __trunc__ returned: it may be * int, long or something with an exposed __int__. - * + * * @return convert to an int. * @throws TypeError and AttributeError. */ @@ -160,7 +158,7 @@ PyObject i = integral.invoke("__int__"); if (!(i instanceof PyInteger) && !(i instanceof PyLong)) { throw Py.TypeError(String.format("__trunc__ returned non-Integral (type %.200s)", - integral.getType().fastGetName())); + integral.getType().fastGetName())); } return i; } @@ -225,7 +223,7 @@ @Override public Object __tojava__(Class c) { if (c == Integer.TYPE || c == Number.class || c == Object.class || c == Integer.class - || c == Serializable.class) { + || c == Serializable.class) { return new Integer(getValue()); } @@ -233,10 +231,10 @@ return new Boolean(getValue() != 0); } if (c == Byte.TYPE || c == Byte.class) { - return new Byte((byte) getValue()); + return new Byte((byte)getValue()); } if (c == Short.TYPE || c == Short.class) { - return new Short((short) getValue()); + return new Short((short)getValue()); } if (c == Long.TYPE || c == Long.class) { @@ -276,8 +274,8 @@ } /** - * Coercion logic for int. Implemented as a final method to avoid - * invocation of virtual methods from the exposed coerced. + * Coercion logic for int. Implemented as a final method to avoid invocation of virtual methods + * from the exposed coerced. */ final Object int___coerce_ex__(PyObject other) { return other instanceof PyInteger ? other : Py.None; @@ -289,7 +287,7 @@ private static final int coerce(PyObject other) { if (other instanceof PyInteger) { - return ((PyInteger) other).getValue(); + return ((PyInteger)other).getValue(); } throw Py.TypeError("xxx"); } @@ -311,7 +309,7 @@ if ((x ^ a) >= 0 || (x ^ b) >= 0) { return Py.newInteger(x); } - return new PyLong((long) a + (long) b); + return new PyLong((long)a + (long)b); } @Override @@ -329,7 +327,7 @@ if ((x ^ a) >= 0 || (x ^ ~b) >= 0) { return Py.newInteger(x); } - return new PyLong((long) a - (long) b); + return new PyLong((long)a - (long)b); } @Override @@ -374,7 +372,7 @@ x *= rightv; if (x <= Integer.MAX_VALUE && x >= Integer.MIN_VALUE) { - return Py.newInteger((int) x); + return Py.newInteger((int)x); } return __long__().__mul__(right); } @@ -400,12 +398,12 @@ // If the signs of x and y differ, and the remainder is non-0, C89 doesn't define // whether xdivy is now the floor or the ceiling of the infinitely precise - // quotient. We want the floor, and we have it iff the remainder's sign matches + // quotient. We want the floor, and we have it iff the remainder's sign matches // y's. if (xmody != 0 && ((y < 0 && xmody > 0) || (y > 0 && xmody < 0))) { xmody += y; --xdivy; - //assert(xmody && ((y ^ xmody) >= 0)); + // assert(xmody && ((y ^ xmody) >= 0)); } return xdivy; } @@ -568,8 +566,8 @@ return int___pow__(right, modulo); } - @ExposedMethod(type = MethodType.BINARY, defaults = {"null"}, - doc = BuiltinDocs.int___pow___doc) + @ExposedMethod(type = MethodType.BINARY, defaults = {"null"}, // + doc = BuiltinDocs.int___pow___doc) final PyObject int___pow__(PyObject right, PyObject modulo) { if (!canCoerce(right)) { return null; @@ -599,8 +597,8 @@ return __rpow__(left, null); } - private static PyObject _pow(int value, int pow, PyObject modulo, PyObject left, - PyObject right) { + private static PyObject _pow(int value, int pow, PyObject modulo,// + PyObject left, PyObject right) { int mod = 0; long tmp = value; boolean neg = false; @@ -663,7 +661,6 @@ return Py.newInteger(result); } - @Override public PyObject __lshift__(PyObject right) { return int___lshift__(right); @@ -673,7 +670,7 @@ final PyObject int___lshift__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__lshift__(right); } else { @@ -696,7 +693,7 @@ final PyObject int___rlshift__(PyObject left) { int leftv; if (left instanceof PyInteger) { - leftv = ((PyInteger) left).getValue(); + leftv = ((PyInteger)left).getValue(); } else if (left instanceof PyLong) { return left.__rlshift__(int___long__()); } else { @@ -724,7 +721,7 @@ final PyObject int___rshift__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__rshift__(right); } else { @@ -746,7 +743,7 @@ final PyObject int___rrshift__(PyObject left) { int leftv; if (left instanceof PyInteger) { - leftv = ((PyInteger) left).getValue(); + leftv = ((PyInteger)left).getValue(); } else if (left instanceof PyLong) { return left.__rshift__(int___long__()); } else { @@ -773,7 +770,7 @@ final PyObject int___and__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__and__(right); } else { @@ -797,7 +794,7 @@ final PyObject int___xor__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__xor__(right); } else { @@ -811,7 +808,7 @@ final PyObject int___rxor__(PyObject left) { int leftv; if (left instanceof PyInteger) { - leftv = ((PyInteger) left).getValue(); + leftv = ((PyInteger)left).getValue(); } else if (left instanceof PyLong) { return left.__rxor__(int___long__()); } else { @@ -830,7 +827,7 @@ final PyObject int___or__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__or__(right); } else { @@ -921,7 +918,7 @@ @ExposedMethod(doc = BuiltinDocs.int___float___doc) final PyFloat int___float__() { - return new PyFloat((double) getValue()); + return new PyFloat((double)getValue()); } @Override @@ -979,7 +976,7 @@ @ExposedMethod(doc = BuiltinDocs.int___getnewargs___doc) final PyTuple int___getnewargs__() { - return new PyTuple(new PyObject[]{new PyInteger(this.getValue())}); + return new PyTuple(new PyObject[] {new PyInteger(this.getValue())}); } @Override @@ -1026,7 +1023,7 @@ throw Py.TypeError("__format__ requires str or unicode"); } - PyString formatSpecStr = (PyString) formatSpec; + PyString formatSpecStr = (PyString)formatSpec; String result; try { String specString = formatSpecStr.getString(); @@ -1052,10 +1049,10 @@ int sign; if (value instanceof Integer) { - int intValue = (Integer) value; + int intValue = (Integer)value; sign = intValue < 0 ? -1 : intValue == 0 ? 0 : 1; } else { - sign = ((BigInteger) value).signum(); + sign = ((BigInteger)value).signum(); } String strValue; @@ -1065,20 +1062,20 @@ if (spec.type == 'c') { if (spec.sign != '\0') { throw new IllegalArgumentException("Sign not allowed with integer format " - + "specifier 'c'"); + + "specifier 'c'"); } if (value instanceof Integer) { - int intValue = (Integer) value; + int intValue = (Integer)value; if (intValue > 0xffff) { throw new IllegalArgumentException("%c arg not in range(0x10000)"); } - strValue = Character.toString((char) intValue); + strValue = Character.toString((char)intValue); } else { - BigInteger bigInt = (BigInteger) value; + BigInteger bigInt = (BigInteger)value; if (bigInt.intValue() > 0xffff || bigInt.bitCount() > 16) { throw new IllegalArgumentException("%c arg not in range(0x10000)"); } - strValue = Character.toString((char) bigInt.intValue()); + strValue = Character.toString((char)bigInt.intValue()); } } else { int radix = 10; @@ -1099,21 +1096,21 @@ } else if (value instanceof BigInteger) { switch (radix) { case 2: - strValue = toBinString((BigInteger) value); + strValue = toBinString((BigInteger)value); break; case 8: - strValue = toOctString((BigInteger) value); + strValue = toOctString((BigInteger)value); break; case 16: - strValue = toHexString((BigInteger) value); + strValue = toHexString((BigInteger)value); break; default: // General case (v.slow in known implementations up to Java 7). - strValue = ((BigInteger) value).toString(radix); + strValue = ((BigInteger)value).toString(radix); break; } } else { - strValue = Integer.toString((Integer) value, radix); + strValue = Integer.toString((Integer)value, radix); } if (spec.alternate) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:09 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:09 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?b?anl0aG9uOiBSZS13b3JrIF9fZm9ybWF0X18s?= =?utf-8?b?IF9fc3RyX18gYW5kIF9fcmVwcl9fIGluIGZsb2F0IGFuZCBjb21wbGV4Lg==?= Message-ID: <3gDbJn3wmWz7Ljk@mail.python.org> http://hg.python.org/jython/rev/d2b41b8d8368 changeset: 7216:d2b41b8d8368 user: Jeff Allen date: Mon Apr 21 16:44:33 2014 +0100 summary: Re-work __format__, __str__ and __repr__ in float and complex. This addresses certain test failures in test_float and test_complex, and one in test_json. files: Lib/test/test_complex.py | 213 ++- Lib/test/test_float.py | 46 +- Lib/test/test_float_jy.py | 19 +- src/org/python/core/PyComplex.java | 110 +- src/org/python/core/PyFloat.java | 91 +- src/org/python/core/stringlib/FloatFormatter.java | 880 ++++++++++ src/org/python/core/stringlib/Formatter.java | 422 ---- src/org/python/core/stringlib/InternalFormat.java | 824 +++++++++ 8 files changed, 2073 insertions(+), 532 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -220,6 +220,7 @@ def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) + @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") def test_constructor(self): class OS: def __init__(self, value): self.value = value @@ -264,6 +265,188 @@ self.assertAlmostEqual(complex("-1"), -1) self.assertAlmostEqual(complex("+1"), +1) #FIXME: these are not working in Jython. + self.assertAlmostEqual(complex("(1+2j)"), 1+2j) + self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j) + # ] + self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j) + #FIXME: these are not working in Jython. + self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j) + self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j) + self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j) + # ] + self.assertAlmostEqual(complex("J"), 1j) + #FIXME: this is not working in Jython. + self.assertAlmostEqual(complex("( j )"), 1j) + # ] + self.assertAlmostEqual(complex("+J"), 1j) + #FIXME: this is not working in Jython. + self.assertAlmostEqual(complex("( -j)"), -1j) + # ] + self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) + self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) + self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) + + class complex2(complex): pass + self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) + self.assertAlmostEqual(complex(real=17, imag=23), 17+23j) + self.assertAlmostEqual(complex(real=17+23j), 17+23j) + self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j) + self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j) + + # check that the sign of a zero in the real or imaginary part + # is preserved when constructing from two floats. (These checks + # are harmless on systems without support for signed zeros.) + def split_zeros(x): + """Function that produces different results for 0. and -0.""" + return atan2(x, -1.) + + self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.)) + #FIXME: this is not working in Jython. + self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.)) + # ] + self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.)) + self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.)) + + c = 3.14 + 1j + self.assertTrue(complex(c) is c) + del c + + self.assertRaises(TypeError, complex, "1", "1") + self.assertRaises(TypeError, complex, 1, "1") + + if test_support.have_unicode: + self.assertEqual(complex(unicode(" 3.14+J ")), 3.14+1j) + + # SF bug 543840: complex(string) accepts strings with \0 + # Fixed in 2.3. + self.assertRaises(ValueError, complex, '1+1j\0j') + + self.assertRaises(TypeError, int, 5+3j) + self.assertRaises(TypeError, long, 5+3j) + self.assertRaises(TypeError, float, 5+3j) + self.assertRaises(ValueError, complex, "") + self.assertRaises(TypeError, complex, None) + self.assertRaises(ValueError, complex, "\0") + self.assertRaises(ValueError, complex, "3\09") + self.assertRaises(TypeError, complex, "1", "2") + self.assertRaises(TypeError, complex, "1", 42) + self.assertRaises(TypeError, complex, 1, "2") + self.assertRaises(ValueError, complex, "1+") + self.assertRaises(ValueError, complex, "1+1j+1j") + self.assertRaises(ValueError, complex, "--") + self.assertRaises(ValueError, complex, "(1+2j") + self.assertRaises(ValueError, complex, "1+2j)") + self.assertRaises(ValueError, complex, "1+(2j)") + self.assertRaises(ValueError, complex, "(1+2j)123") + if test_support.have_unicode: + self.assertRaises(ValueError, complex, unicode("x")) + #FIXME: these are raising wrong errors in Jython. + self.assertRaises(ValueError, complex, "1j+2") + self.assertRaises(ValueError, complex, "1e1ej") + self.assertRaises(ValueError, complex, "1e++1ej") + self.assertRaises(ValueError, complex, ")1+2j(") + # ] + + # the following three are accepted by Python 2.6 + #FIXME: these are raising wrong errors in Jython. + self.assertRaises(ValueError, complex, "1..1j") + self.assertRaises(ValueError, complex, "1.11.1j") + self.assertRaises(ValueError, complex, "1e1.1j") + # ] + + #FIXME: not working in Jython. + if test_support.have_unicode: + # check that complex accepts long unicode strings + self.assertEqual(type(complex(unicode("1"*500))), complex) + # ] + + class EvilExc(Exception): + pass + + class evilcomplex: + def __complex__(self): + raise EvilExc + + self.assertRaises(EvilExc, complex, evilcomplex()) + + class float2: + def __init__(self, value): + self.value = value + def __float__(self): + return self.value + + self.assertAlmostEqual(complex(float2(42.)), 42) + self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j) + self.assertRaises(TypeError, complex, float2(None)) + + class complex0(complex): + """Test usage of __complex__() when inheriting from 'complex'""" + def __complex__(self): + return 42j + + class complex1(complex): + """Test usage of __complex__() with a __new__() method""" + def __new__(self, value=0j): + return complex.__new__(self, 2*value) + def __complex__(self): + return self + + class complex2(complex): + """Make sure that __complex__() calls fail if anything other than a + complex is returned""" + def __complex__(self): + return None + + self.assertAlmostEqual(complex(complex0(1j)), 42j) + self.assertAlmostEqual(complex(complex1(1j)), 2j) + self.assertRaises(TypeError, complex, complex2(1j)) + + def test_constructor_jy(self): + # These are the parts of test_constructor that work in Jython. + # Delete this test when test_constructor skip is removed. + class OS: + def __init__(self, value): self.value = value + def __complex__(self): return self.value + class NS(object): + def __init__(self, value): self.value = value + def __complex__(self): return self.value + self.assertEqual(complex(OS(1+10j)), 1+10j) + self.assertEqual(complex(NS(1+10j)), 1+10j) + self.assertRaises(TypeError, complex, OS(None)) + self.assertRaises(TypeError, complex, NS(None)) + + self.assertAlmostEqual(complex("1+10j"), 1+10j) + self.assertAlmostEqual(complex(10), 10+0j) + self.assertAlmostEqual(complex(10.0), 10+0j) + self.assertAlmostEqual(complex(10L), 10+0j) + self.assertAlmostEqual(complex(10+0j), 10+0j) + self.assertAlmostEqual(complex(1,10), 1+10j) + self.assertAlmostEqual(complex(1,10L), 1+10j) + self.assertAlmostEqual(complex(1,10.0), 1+10j) + self.assertAlmostEqual(complex(1L,10), 1+10j) + self.assertAlmostEqual(complex(1L,10L), 1+10j) + self.assertAlmostEqual(complex(1L,10.0), 1+10j) + self.assertAlmostEqual(complex(1.0,10), 1+10j) + self.assertAlmostEqual(complex(1.0,10L), 1+10j) + self.assertAlmostEqual(complex(1.0,10.0), 1+10j) + self.assertAlmostEqual(complex(3.14+0j), 3.14+0j) + self.assertAlmostEqual(complex(3.14), 3.14+0j) + self.assertAlmostEqual(complex(314), 314.0+0j) + self.assertAlmostEqual(complex(314L), 314.0+0j) + self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j) + self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j) + self.assertAlmostEqual(complex(314, 0), 314.0+0j) + self.assertAlmostEqual(complex(314L, 0L), 314.0+0j) + self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j) + self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j) + self.assertAlmostEqual(complex(0j, 3.14), 3.14j) + self.assertAlmostEqual(complex(0.0, 3.14), 3.14j) + self.assertAlmostEqual(complex("1"), 1+0j) + self.assertAlmostEqual(complex("1j"), 1j) + self.assertAlmostEqual(complex(), 0) + self.assertAlmostEqual(complex("-1"), -1) + self.assertAlmostEqual(complex("+1"), +1) + #FIXME: these are not working in Jython. #self.assertAlmostEqual(complex("(1+2j)"), 1+2j) #self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j) self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j) @@ -458,7 +641,7 @@ for num in nums: self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num)) - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") + @unittest.skipIf(test_support.is_jython, "FIXME: str.__complex__ not working in Jython") def test_repr(self): self.assertEqual(repr(1+6j), '(1+6j)') self.assertEqual(repr(1-6j), '(1-6j)') @@ -482,6 +665,32 @@ self.assertEqual(repr(complex(0, -INF)), "-infj") self.assertEqual(repr(complex(0, NAN)), "nanj") + def test_repr_jy(self): + # These are just the cases that Jython can do from test_repr + # Delete this test when test_repr passes + self.assertEqual(repr(1+6j), '(1+6j)') + self.assertEqual(repr(1-6j), '(1-6j)') + + self.assertNotEqual(repr(-(1+0j)), '(-1+-0j)') + + # Fails to round-trip: +# self.assertEqual(1-6j,complex(repr(1-6j))) +# self.assertEqual(1+6j,complex(repr(1+6j))) +# self.assertEqual(-6j,complex(repr(-6j))) +# self.assertEqual(6j,complex(repr(6j))) + + self.assertEqual(repr(complex(1., INF)), "(1+infj)") + self.assertEqual(repr(complex(1., -INF)), "(1-infj)") + self.assertEqual(repr(complex(INF, 1)), "(inf+1j)") + self.assertEqual(repr(complex(-INF, INF)), "(-inf+infj)") + self.assertEqual(repr(complex(NAN, 1)), "(nan+1j)") + self.assertEqual(repr(complex(1, NAN)), "(1+nanj)") + self.assertEqual(repr(complex(NAN, NAN)), "(nan+nanj)") + + self.assertEqual(repr(complex(0, INF)), "infj") + self.assertEqual(repr(complex(0, -INF)), "-infj") + self.assertEqual(repr(complex(0, NAN)), "nanj") + def test_neg(self): self.assertEqual(-(1+6j), -1-6j) @@ -501,7 +710,6 @@ fo.close() test_support.unlink(test_support.TESTFN) - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") def test_getnewargs(self): self.assertEqual((1+2j).__getnewargs__(), (1.0, 2.0)) self.assertEqual((1-2j).__getnewargs__(), (1.0, -2.0)) @@ -557,7 +765,6 @@ self.assertFloatsAreIdentical(0.0 + z.imag, 0.0 + roundtrip.imag) - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") def test_format(self): # empty format string is same as str() self.assertEqual(format(1+3j, ''), str(1+3j)) diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -638,16 +638,12 @@ if not math.isnan(arg) and copysign(1.0, arg) > 0.0: self.assertEqual(fmt % -arg, '-' + rhs) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working on Jython") def test_issue5864(self): self.assertEqual(format(123.456, '.4'), '123.5') self.assertEqual(format(1234.56, '.4'), '1.235e+03') self.assertEqual(format(12345.6, '.4'), '1.235e+04') class ReprTestCase(unittest.TestCase): - @unittest.skipIf(test_support.is_jython, - "FIXME: not working on Jython") def test_repr(self): floats_file = open(os.path.join(os.path.split(__file__)[0], 'floating_points.txt')) @@ -768,6 +764,8 @@ self.assertRaises(OverflowError, round, 1.6e308, -308) self.assertRaises(OverflowError, round, -1.7e308, -308) + @unittest.skipIf(test_support.is_jython, + "FIXME: rounding incorrect in Jython") @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short', "test applies only when using short float repr style") def test_previous_round_bugs(self): @@ -777,6 +775,8 @@ self.assertEqual(round(56294995342131.5, 3), 56294995342131.5) + @unittest.skipIf(test_support.is_jython, + "FIXME: rounding incorrect in Jython") @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short', "test applies only when using short float repr style") def test_halfway_cases(self): @@ -855,7 +855,7 @@ @unittest.skipIf(test_support.is_jython, - "FIXME: formatting specials imperfect in Jython") + "FIXME: %-formatting specials imperfect in Jython") @requires_IEEE_754 def test_format_specials(self): # Test formatting of nans and infs. @@ -890,6 +890,42 @@ test(sfmt, NAN, ' nan') test(sfmt, -NAN, ' nan') + @requires_IEEE_754 + def test_format_specials_jy(self): + # Test formatting of nans and infs (suppressing %-formatting). + # This is just a crudely restricted copy of test_format_specials. + # Delete this test when we no longer have to skip test_format_specials. + + def test(fmt, value, expected): + # Test with only format(). + #self.assertEqual(fmt % value, expected, fmt) + if not '#' in fmt: + # Until issue 7094 is implemented, format() for floats doesn't + # support '#' formatting + fmt = fmt[1:] # strip off the % + self.assertEqual(format(value, fmt), expected, fmt) + + for fmt in ['%e', '%f', '%g', '%.0e', '%.6f', '%.20g', + '%#e', '%#f', '%#g', '%#.20e', '%#.15f', '%#.3g']: + pfmt = '%+' + fmt[1:] + sfmt = '% ' + fmt[1:] + test(fmt, INF, 'inf') + test(fmt, -INF, '-inf') + test(fmt, NAN, 'nan') + test(fmt, -NAN, 'nan') + # When asking for a sign, it's always provided. nans are + # always positive. + test(pfmt, INF, '+inf') + test(pfmt, -INF, '-inf') + test(pfmt, NAN, '+nan') + test(pfmt, -NAN, '+nan') + # When using ' ' for a sign code, only infs can be negative. + # Others have a space. + test(sfmt, INF, ' inf') + test(sfmt, -INF, '-inf') + test(sfmt, NAN, ' nan') + test(sfmt, -NAN, ' nan') + # Beginning with Python 2.6 float has cross platform compatible # ways to create and represent inf and nan diff --git a/Lib/test/test_float_jy.py b/Lib/test/test_float_jy.py --- a/Lib/test/test_float_jy.py +++ b/Lib/test/test_float_jy.py @@ -14,19 +14,22 @@ def test_float_repr(self): self.assertEqual(repr(12345678.000000005), '12345678.000000006') self.assertEqual(repr(12345678.0000000005), '12345678.0') - self.assertEqual(repr(math.pi**-100), - jython and '1.9275814160560203e-50' or '1.9275814160560206e-50') + self.assertEqual(repr(math.pi**-100), '1.9275814160560206e-50') self.assertEqual(repr(-1.0), '-1.0') - self.assertEqual(repr(-9876.543210), - jython and '-9876.54321' or '-9876.5432099999998') + self.assertEqual(repr(-9876.543210), '-9876.54321') self.assertEqual(repr(0.123456789e+35), '1.23456789e+34') + def test_float_repr2(self): + # Quite possibly these divergences result from JDK bug JDK-4511638: + self.assertEqual(repr(9876.543210e+15), + jython and '9.876543209999999e+18' or '9.87654321e+18') + self.assertEqual(repr(1235235235235240000.0), + jython and '1.2352352352352399e+18' or '1.23523523523524e+18') + def test_float_str(self): self.assertEqual(str(12345678.000005), '12345678.0') - self.assertEqual(str(12345678.00005), - jython and '12345678.0' or '12345678.0001') - self.assertEqual(str(12345678.00005), - jython and '12345678.0' or '12345678.0001') + self.assertEqual(str(12345678.00005), '12345678.0001') + self.assertEqual(str(12345678.00005), '12345678.0001') self.assertEqual(str(12345678.0005), '12345678.0005') self.assertEqual(str(math.pi**-100), '1.92758141606e-50') self.assertEqual(str(0.0), '0.0') diff --git a/src/org/python/core/PyComplex.java b/src/org/python/core/PyComplex.java --- a/src/org/python/core/PyComplex.java +++ b/src/org/python/core/PyComplex.java @@ -2,9 +2,9 @@ // Copyright (c) Jython Developers package org.python.core; -import org.python.core.stringlib.Formatter; -import org.python.core.stringlib.InternalFormatSpec; -import org.python.core.stringlib.InternalFormatSpecParser; +import org.python.core.stringlib.FloatFormatter; +import org.python.core.stringlib.InternalFormat; +import org.python.core.stringlib.InternalFormat.Spec; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; @@ -19,6 +19,11 @@ public static final PyType TYPE = PyType.fromClass(PyComplex.class); + /** Format specification used by repr(). */ + static final Spec SPEC_REPR = InternalFormat.fromText(" >r"); // but also minFracDigits=0 + /** Format specification used by str() and none-format. (As CPython, but is that right?) */ + static final Spec SPEC_STR = InternalFormat.fromText(" >.12g"); + static PyComplex J = new PyComplex(0, 1.); @ExposedGet(doc = BuiltinDocs.complex_real_doc) @@ -132,20 +137,51 @@ @Override public String toString() { - return complex_toString(); + return __str__().toString(); } - @ExposedMethod(names = {"__repr__", "__str__"}, doc = BuiltinDocs.complex___str___doc) - final String complex_toString() { - if (real == 0.) { - return toString(imag) + "j"; + @Override + public PyString __str__() { + return complex___str__(); + } + + @ExposedMethod(doc = BuiltinDocs.complex___str___doc) + final PyString complex___str__() { + return Py.newString(formatComplex(SPEC_STR)); + } + + @Override + public PyString __repr__() { + return complex___repr__(); + } + + @ExposedMethod(doc = BuiltinDocs.complex___repr___doc) + final PyString complex___repr__() { + return Py.newString(formatComplex(SPEC_REPR)); + } + + /** + * Format this complex according to the specification passed in. Supports __str__ + * and __repr__, and none-format. + *

+ * In general, the output is surrounded in parentheses, like "(12.34+24.68j)". + * However, if the real part is zero, only the imaginary part is printed, and without + * parentheses like "24.68j". The number format specification passed in is used + * without padding to width, for the real and imaginary parts individually. + * + * @param spec parsed format specification string + * @return formatted value + */ + private String formatComplex(Spec spec) { + FloatFormatter f = new FloatFormatter(spec, 2, 3); // Two elements + "(j)".length + // Even in r-format, complex strips *all* the trailing zeros. + f.setMinFracDigits(0); + if (real == 0.0) { + f.format(imag).append('j'); } else { - if (imag >= 0) { - return String.format("(%s+%sj)", toString(real), toString(imag)); - } else { - return String.format("(%s-%sj)", toString(real), toString(-imag)); - } + f.append('(').format(real).format(imag, "+").append("j)").pad(); } + return f.getResult(); } @Override @@ -763,7 +799,7 @@ @ExposedMethod(doc = BuiltinDocs.complex___getnewargs___doc) final PyTuple complex___getnewargs__() { - return new PyTuple(new PyComplex(real, imag)); + return new PyTuple(new PyFloat(real), new PyFloat(imag)); } @Override @@ -778,10 +814,6 @@ @ExposedMethod(doc = BuiltinDocs.complex___format___doc) final PyObject complex___format__(PyObject formatSpec) { - return formatImpl(real, imag, formatSpec); - } - - static PyObject formatImpl(double r, double i, PyObject formatSpec) { if (!(formatSpec instanceof PyString)) { throw Py.TypeError("__format__ requires str or unicode"); } @@ -790,26 +822,32 @@ String result; try { String specString = formatSpecStr.getString(); - InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse(); - switch (spec.type) { - case '\0': // No format code: like 'g', but with at least one decimal. - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case 'n': - case '%': - result = Formatter.formatComplex(r, i, spec); - break; - default: - /* unknown */ - throw Py.ValueError(String.format( - "Unknown format code '%c' for object of type 'complex'", spec.type)); + Spec spec = InternalFormat.fromText(specString); + if (spec.type!=Spec.NONE && "efgEFGn%".indexOf(spec.type) < 0) { + throw FloatFormatter.unknownFormat(spec.type, "complex"); + } else if (spec.alternate) { + throw FloatFormatter.alternateFormNotAllowed("complex"); + } else if (spec.fill == '0') { + throw FloatFormatter.zeroPaddingNotAllowed("complex"); + } else if (spec.align == '=') { + throw FloatFormatter.alignmentNotAllowed('=', "complex"); + } else { + if (spec.type == Spec.NONE) { + // In none-format, we take the default type and precision from __str__. + spec = spec.withDefaults(SPEC_STR); + // And then we use the __str__ mechanism to get parentheses or real 0 elision. + result = formatComplex(spec); + } else { + // In any other format, the defaults those commonly used for numeric formats. + spec = spec.withDefaults(Spec.NUMERIC); + FloatFormatter f = new FloatFormatter(spec, 2, 1);// 2 floats + "j" + // Convert as both parts per specification + f.format(real).format(imag, "+").append('j').pad(); + result = f.getResult(); + } } } catch (IllegalArgumentException e) { - throw Py.ValueError(e.getMessage()); + throw Py.ValueError(e.getMessage()); // XXX Can this be reached? } return formatSpecStr.createInstance(result); } diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java --- a/src/org/python/core/PyFloat.java +++ b/src/org/python/core/PyFloat.java @@ -5,9 +5,9 @@ import java.io.Serializable; import java.math.BigDecimal; -import org.python.core.stringlib.Formatter; -import org.python.core.stringlib.InternalFormatSpec; -import org.python.core.stringlib.InternalFormatSpecParser; +import org.python.core.stringlib.FloatFormatter; +import org.python.core.stringlib.InternalFormat; +import org.python.core.stringlib.InternalFormat.Spec; import org.python.expose.ExposedClassMethod; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; @@ -24,9 +24,10 @@ public static final PyType TYPE = PyType.fromClass(PyFloat.class); - /** Precisions used by repr() and str(), respectively. */ - private static final int PREC_REPR = 17; - private static final int PREC_STR = 12; + /** Format specification used by repr(). */ + static final Spec SPEC_REPR = InternalFormat.fromText(" >r"); + /** Format specification used by str(). */ + static final Spec SPEC_STR = Spec.NUMERIC; private final double value; @@ -224,7 +225,7 @@ @ExposedMethod(doc = BuiltinDocs.float___str___doc) final PyString float___str__() { - return Py.newString(formatDouble(PREC_STR)); + return Py.newString(formatDouble(SPEC_STR)); } @Override @@ -234,34 +235,19 @@ @ExposedMethod(doc = BuiltinDocs.float___repr___doc) final PyString float___repr__() { - return Py.newString(formatDouble(PREC_REPR)); + return Py.newString(formatDouble(SPEC_REPR)); } - private String formatDouble(int precision) { - if (Double.isNaN(value)) { - return "nan"; - } else if (value == Double.NEGATIVE_INFINITY) { - return "-inf"; - } else if (value == Double.POSITIVE_INFINITY) { - return "inf"; - } - - String result = String.format("%%.%dg", precision); - result = Py.newString(result).__mod__(this).toString(); - - int i = 0; - if (result.startsWith("-")) { - i++; - } - for (; i < result.length(); i++) { - if (!Character.isDigit(result.charAt(i))) { - break; - } - } - if (i == result.length()) { - result += ".0"; - } - return result; + /** + * Format this float according to the specification passed in. Supports __str__ and + * __repr__. + * + * @param spec parsed format specification string + * @return formatted value + */ + private String formatDouble(Spec spec) { + FloatFormatter f = new FloatFormatter(spec); + return f.format(value).getResult(); } @Override @@ -919,10 +905,6 @@ @ExposedMethod(doc = BuiltinDocs.float___format___doc) final PyObject float___format__(PyObject formatSpec) { - return formatImpl(getValue(), formatSpec); - } - - static PyObject formatImpl(double d, PyObject formatSpec) { if (!(formatSpec instanceof PyString)) { throw Py.TypeError("__format__ requires str or unicode"); } @@ -931,29 +913,22 @@ String result; try { String specString = formatSpecStr.getString(); - InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse(); - if (spec.type == '\0') { - return (Py.newFloat(d)).__str__(); - } - switch (spec.type) { - case '\0': // No format code: like 'g', but with at least one decimal. - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case 'n': - case '%': - result = Formatter.formatFloat(d, spec); - break; - default: - /* unknown */ - throw Py.ValueError(String.format( - "Unknown format code '%c' for object of type 'float'", spec.type)); + Spec spec = InternalFormat.fromText(specString); + if (spec.type!=Spec.NONE && "efgEFGn%".indexOf(spec.type) < 0) { + throw FloatFormatter.unknownFormat(spec.type, "float"); + } else if (spec.alternate) { + throw FloatFormatter.alternateFormNotAllowed("float"); + } else { + // spec may be incomplete. The defaults are those commonly used for numeric formats. + spec = spec.withDefaults(Spec.NUMERIC); + // Get a formatter for the spec. + FloatFormatter f = new FloatFormatter(spec); + // Convert as per specification. + f.format(value).pad(); + result = f.getResult(); } } catch (IllegalArgumentException e) { - throw Py.ValueError(e.getMessage()); + throw Py.ValueError(e.getMessage()); // XXX Can this be reached? } return formatSpecStr.createInstance(result); } diff --git a/src/org/python/core/stringlib/FloatFormatter.java b/src/org/python/core/stringlib/FloatFormatter.java new file mode 100644 --- /dev/null +++ b/src/org/python/core/stringlib/FloatFormatter.java @@ -0,0 +1,880 @@ +// Copyright (c) Jython Developers +package org.python.core.stringlib; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.python.core.stringlib.InternalFormat.Spec; + +/** + * A class that provides the implementation of floating-point formatting. In a limited way, it acts + * like a StringBuilder to which text and one or more numbers may be appended, formatted according + * to the format specifier supplied at construction. These are ephemeral objects that are not, on + * their own, thread safe. + */ +public class FloatFormatter extends InternalFormat.Formatter { + + /** The rounding mode dominant in the formatter. */ + static final RoundingMode ROUND_PY = RoundingMode.HALF_UP; // Believed to be HALF_EVEN in Py3k + + /** If it contains no decimal point, this length is zero, and 1 otherwise. */ + private int lenPoint; + /** The length of the fractional part, right of the decimal point. */ + private int lenFraction; + /** The length of the exponent marker ("e"), "inf" or "nan", or zero if there isn't one. */ + private int lenMarker; + /** The length of the exponent sign and digits or zero if there isn't one. */ + private int lenExponent; + /** if >=0, minimum digits to follow decimal point (where consulted) */ + private int minFracDigits; + + /** + * Construct the formatter from a specification. A reference is held to this specification, but + * it will not be modified by the actions of this class. + * + * @param spec parsed conversion specification + */ + public FloatFormatter(Spec spec) { + // Space for result is based on padded width, or precision, whole part & furniture. + this(spec, 1, 0); + } + + /** + * Construct the formatter from a specification and an explicit initial buffer capacity. A + * reference is held to this specification, but it will not be modified by the actions of this + * class. + * + * @param spec parsed conversion specification + * @param width expected for the formatted result + */ + public FloatFormatter(Spec spec, int width) { + super(spec, width); + if (spec.alternate) { + // Alternate form means do not trim the zero fractional digits. + minFracDigits = -1; + } else if (spec.type == 'r' || spec.type == Spec.NONE) { + // These formats by default show at least one fractional digit. + minFracDigits = 1; + } else { + /* + * Every other format (if it does not ignore the setting) will by default trim off all + * the trailing zero fractional digits. + */ + minFracDigits = 0; + } + } + + /** + * Construct the formatter from a specification and two extra hints about the initial buffer + * capacity. A reference is held to this specification, but it will not be modified by the + * actions of this class. + * + * @param spec parsed conversion specification + * @param count of elements likely to be formatted + * @param margin for elements formatted only once + */ + public FloatFormatter(Spec spec, int count, int margin) { + /* + * Rule of thumb used here: in e format w = (p-1) + len("+1.e+300") = p+7; in f format w = p + * + len("1,000,000.") = p+10. If we're wrong, the result will have to grow. No big deal. + */ + this(spec, Math.max(spec.width + 1, count * (spec.precision + 10) + margin)); + } + + /** + * Override the default truncation behaviour for the specification originally supplied. Some + * formats remove trailing zero digits, trimming to zero or one. Set member + * minFracDigits, to modify this behaviour. + * + * @param minFracDigits if <0 prevent truncation; if >=0 the minimum number of fractional + * digits; when this is zero, and all fractional digits are zero, the decimal point + * will also be removed. + */ + public void setMinFracDigits(int minFracDigits) { + this.minFracDigits = minFracDigits; + } + + @Override + protected void reset() { + // Clear the variables describing the latest number in result. + super.reset(); + lenPoint = lenFraction = lenMarker = lenExponent = 0; + } + + @Override + protected int[] sectionLengths() { + return new int[] {lenSign, lenWhole, lenPoint, lenFraction, lenMarker, lenExponent}; + } + + /* + * Re-implement the text appends so they return the right type. + */ + @Override + public FloatFormatter append(char c) { + super.append(c); + return this; + } + + @Override + public FloatFormatter append(CharSequence csq) { + super.append(csq); + return this; + } + + @Override + public FloatFormatter append(CharSequence csq, int start, int end) // + throws IndexOutOfBoundsException { + super.append(csq, start, end); + return this; + } + + /** + * Format a floating-point number according to the specification represented by this + * FloatFormatter. + * + * @param value to convert + * @return this object + */ + public FloatFormatter format(double value) { + return format(value, null); + } + + /** + * Format a floating-point number according to the specification represented by this + * FloatFormatter. The conversion type, precision, and flags for grouping or + * percentage are dealt with here. At the point this is used, we know the {@link #spec} is one + * of the floating-point types. This entry point allows explicit control of the prefix of + * positive numbers, overriding defaults for the format type. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @return this object + */ + @SuppressWarnings("fallthrough") + public FloatFormatter format(double value, String positivePrefix) { + + // Puts all instance variables back to their starting defaults, and start = result.length(). + setStart(); + + // Precision defaults to 6 (or 12 for none-format) + int precision = spec.getPrecision(Spec.specified(spec.type) ? 6 : 12); + + /* + * By default, the prefix of a positive number is "", but the format specifier may override + * it, and the built-in type complex needs to override the format. + */ + if (positivePrefix == null && Spec.specified(spec.sign) && spec.sign != '-') { + positivePrefix = Character.toString(spec.sign); + } + + // Different process for each format type, ignoring case for now. + switch (Character.toLowerCase(spec.type)) { + case 'e': + // Exponential case: 1.23e-45 + format_e(value, positivePrefix, precision); + break; + + case 'f': + // Fixed case: 123.45 + format_f(value, positivePrefix, precision); + break; + + case 'n': + // Locale-sensitive version of g-format should be here. (D?sol? de vous decevoir.) + // XXX Set a variable here to signal localisation in/after groupDigits? + case 'g': + // General format: fixed or exponential according to value. + format_g(value, positivePrefix, precision, 0); + break; + + case Spec.NONE: + // None format like g-format but goes exponential at precision-1 + format_g(value, positivePrefix, precision, -1); + break; + + case 'r': + // For float.__repr__, very special case, breaks all the rules. + format_r(value, positivePrefix); + break; + + case '%': + // Multiplies by 100 and displays in f-format, followed by a percent sign. + format_f(100. * value, positivePrefix, precision); + result.append('%'); + break; + + default: + // Should never get here, since this was checked in PyFloat. + throw unknownFormat(spec.type, "float"); + } + + // If the format type is an upper-case letter, convert the result to upper case. + if (Character.isUpperCase(spec.type)) { + uppercase(); + } + + // If required to, group the whole-part digits. + if (spec.grouping) { + groupDigits(3, ','); + } + + return this; + } + + /** + * Convert just the letters in the representation of the current number (in {@link #result}) to + * upper case. (That's the exponent marker or the "inf" or "nan".) + */ + @Override + protected void uppercase() { + int letters = indexOfMarker(); + int end = letters + lenMarker; + for (int i = letters; i < end; i++) { + char c = result.charAt(i); + result.setCharAt(i, Character.toUpperCase(c)); + } + } + + /** + * Common code to deal with the sign, and the special cases "0", "-0", "nan, "inf", or "-inf". + * If the method returns false, we have started a non-zero number and the sign is + * already in {@link #result}. The client need then only encode abs(value). If the method + * returns true, and {@link #lenMarker}==0, the value was "0" or "-0": the caller + * may have to zero-extend this, and/or add an exponent, to match the requested format. If the + * method returns true, and {@link #lenMarker}>0, the method has placed "nan, "inf" + * in the {@link #result} buffer (preceded by a sign if necessary). + * + * @param value to convert + * @return true if the value was one of "0", "-0", "nan, "inf", or "-inf". + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + */ + private boolean signAndSpecialNumber(double value, String positivePrefix) { + + // This is easiest via the raw bits + long bits = Double.doubleToRawLongBits(value); + + // NaN is always positive + if (Double.isNaN(value)) { + bits &= ~SIGN_MASK; + } + + if ((bits & SIGN_MASK) != 0) { + // Negative: encode a minus sign and strip it off bits + result.append('-'); + lenSign = 1; + bits &= ~SIGN_MASK; + + } else if (positivePrefix != null) { + // Positive, and a prefix is required. Note CPython 2.7 produces "+nan", " nan". + result.append(positivePrefix); + lenSign = positivePrefix.length(); + } + + if (bits == 0L) { + // All zero means it's zero. (It may have been negative, producing -0.) + result.append('0'); + lenWhole = 1; + return true; + + } else if ((bits & EXP_MASK) == EXP_MASK) { + // This is characteristic of NaN or Infinity. + result.append(((bits & ~EXP_MASK) == 0L) ? "inf" : "nan"); + lenMarker = 3; + return true; + + } else { + return false; + } + } + + private static final long SIGN_MASK = 0x8000000000000000L; + private static final long EXP_MASK = 0x7ff0000000000000L; + + /** + * The e-format helper function of {@link #format(double, String)} that uses Java's + * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to + * the {@link #result} buffer, and {@link #start} will be set to the index of its first + * character. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @param precision precision (maximum number of fractional digits) + */ + private void format_e(double value, String positivePrefix, int precision) { + + // Exponent (default value is for 0.0 and -0.0) + int exp = 0; + + if (!signAndSpecialNumber(value, positivePrefix)) { + // Convert abs(value) to decimal with p+1 digits of accuracy. + MathContext mc = new MathContext(precision + 1, ROUND_PY); + BigDecimal vv = new BigDecimal(Math.abs(value), mc); + + // Take explicit control in order to get exponential notation out of BigDecimal. + String digits = vv.unscaledValue().toString(); + int digitCount = digits.length(); + result.append(digits.charAt(0)); + lenWhole = 1; + if (digitCount > 1) { + // There is a fractional part + result.append('.').append(digits.substring(1)); + lenPoint = 1; + lenFraction = digitCount - 1; + } + exp = lenFraction - vv.scale(); + } + + // Finally add zeros, as necessary, and stick on the exponent. + + if (lenMarker == 0) { + appendTrailingZeros(precision); + appendExponent(exp); + } + } + + /** + * The f-format inner helper function of {@link #format(double, String)} that uses Java's + * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to + * the {@link #result} buffer, and {@link #start} will be set to the index of its first + * character. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @param precision precision (maximum number of fractional digits) + */ + private void format_f(double value, String positivePrefix, int precision) { + + if (signAndSpecialNumber(value, positivePrefix)) { + + if (lenMarker == 0) { + // May be 0 or -0 so we still need to ... + appendTrailingZeros(precision); + } + + } else { + // Convert value to decimal exactly. (This can be very long.) + BigDecimal vLong = new BigDecimal(Math.abs(value)); + + // Truncate to the defined number of places to the right of the decimal point). + BigDecimal vv = vLong.setScale(precision, ROUND_PY); + + // When converted to text, the number of fractional digits is exactly the scale we set. + String raw = vv.toPlainString(); + result.append(raw); + if ((lenFraction = vv.scale()) > 0) { + // There is a decimal point and some digits following + lenWhole = result.length() - (start + lenSign + (lenPoint = 1) + lenFraction); + } else { + lenWhole = result.length() - (start + lenSign); + } + + } + } + + /** + * Implementation of the variants of g-format, that uses Java's {@link BigDecimal} to provide + * conversion and rounding. These variants are g-format proper, alternate g-format (available + * for "%#g" formatting), n-format (as g but subsequently "internationalised"), and none-format + * (type code Spec.NONE). + *

+ * None-format is the basis of float.__str__. + *

+ * According to the Python documentation for g-format, the precise rules are as follows: suppose + * that the result formatted with presentation type 'e' and precision p-1 + * would have exponent exp. Then if -4 <= exp < p, the number is formatted with + * presentation type 'f' and precision p-1-exp. Otherwise, the number is + * formatted with presentation type 'e' and precision p-1. In both cases + * insignificant trailing zeros are removed from the significand, and the decimal point is also + * removed if there are no remaining digits following it. + *

+ * The Python documentation says none-format is the same as g-format, but the observed behaviour + * differs from this, in that f-format is only used if -4 <= exp < p-1 (i.e. one + * less), and at least one digit to the right of the decimal point is preserved in the f-format + * (but not the e-format). That behaviour is controlled through the following arguments, with + * these recommended values: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
typeprecisionminFracDigitsexpThresholdAdjexpThreshold
gp00p
#gp-0p
\0p1-1p-1
__str__121-111
+ * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @param precision total number of significant digits (precision 0 behaves as 1) + * @param expThresholdAdj +precision = the exponent at which to resume using + * exponential notation + */ + private void format_g(double value, String positivePrefix, int precision, int expThresholdAdj) { + + // Precision 0 behaves as 1 + precision = Math.max(1, precision); + + // Use exponential notation if exponent would be bigger thatn: + int expThreshold = precision + expThresholdAdj; + + if (signAndSpecialNumber(value, positivePrefix)) { + // Finish formatting if zero result. (This is a no-op for nan or inf.) + zeroHelper(precision, expThreshold); + + } else { + + // Convert abs(value) to decimal with p digits of accuracy. + MathContext mc = new MathContext(precision, ROUND_PY); + BigDecimal vv = new BigDecimal(Math.abs(value), mc); + + // This gives us the digits we need for either fixed or exponential format. + String pointlessDigits = vv.unscaledValue().toString(); + + // If we were to complete this as e-format, the exponent would be: + int exp = pointlessDigits.length() - vv.scale() - 1; + + if (-4 <= exp && exp < expThreshold) { + // Finish the job as f-format with variable-precision p-(exp+1). + appendFixed(pointlessDigits, exp, precision); + + } else { + // Finish the job as e-format. + appendExponential(pointlessDigits, exp); + } + } + } + + /** + * Implementation of r-format (float.__repr__) that uses Java's + * {@link Double#toString(double)} to provide conversion and rounding. That method gives us + * almost what we need, but not quite (sometimes it yields 18 digits): here we always round to + * 17 significant digits. Much of the formatting after conversion is shared with + * {@link #format_g(double, String, int, int, int)}. minFracDigits is consulted + * since while float.__repr__ truncates to one digit, within + * complex.__repr__ we truncate fully. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + */ + private void format_r(double value, String positivePrefix) { + + // Characteristics of repr (precision = 17 and go exponential at 16). + int precision = 17; + int expThreshold = precision - 1; + + if (signAndSpecialNumber(value, positivePrefix)) { + // Finish formatting if zero result. (This is a no-op for nan or inf.) + zeroHelper(precision, expThreshold); + + } else { + + // Generate digit sequence (with no decimal point) with custom rounding. + StringBuilder pointlessBuffer = new StringBuilder(20); + int exp = reprDigits(Math.abs(value), precision, pointlessBuffer); + + if (-4 <= exp && exp < expThreshold) { + // Finish the job as f-format with variable-precision p-(exp+1). + appendFixed(pointlessBuffer, exp, precision); + + } else { + // Finish the job as e-format. + appendExponential(pointlessBuffer, exp); + } + } + } + + /** + * Common code for g-format, none-format and r-format called when the conversion yields "inf", + * "nan" or zero. The method completes formatting of the zero, with the appropriate number of + * decimal places or (in particular circumstances) exponential; notation. + * + * @param precision of conversion (number of significant digits). + * @param expThreshold if zero, causes choice of exponential notation for zero. + */ + private void zeroHelper(int precision, int expThreshold) { + + if (lenMarker == 0) { + // May be 0 or -0 so we still need to ... + if (minFracDigits < 0) { + // In "alternate format", we won't economise trailing zeros. + appendPointAndTrailingZeros(precision - 1); + } else if (lenFraction < minFracDigits) { + // Otherwise, it should be at least the stated minimum length. + appendTrailingZeros(minFracDigits); + } + + // And just occasionally (in none-format) we go exponential even when exp = 0... + if (0 >= expThreshold) { + appendExponent(0); + } + } + } + + /** + * Common code for g-format, none-format and r-format used when the exponent is such that a + * fixed-point presentation is chosen. Normally the method removes trailing digits so as to + * shorten the presentation without loss of significance. This method respects the minimum + * number of fractional digits (digits after the decimal point), in member + * minFracDigits, which is 0 for g-format and 1 for none-format and r-format. When + * minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros + * generated in the conversion are not removed. This supports "%#g" format. + * + * @param digits from converting the value at a given precision. + * @param exp would be the exponent (in e-format), used to position the decimal point. + * @param precision of conversion (number of significant digits). + */ + + private void appendFixed(CharSequence digits, int exp, int precision) { + + // Check for "alternate format", where we won't economise trailing zeros. + boolean noTruncate = (minFracDigits < 0); + + int digitCount = digits.length(); + + if (exp < 0) { + // For a negative exponent, we must insert leading zeros 0.000 ... + result.append("0."); + lenWhole = lenPoint = 1; + for (int i = -1; i > exp; --i) { + result.append('0'); + } + // Then the generated digits (always enough to satisfy no-truncate mode). + result.append(digits); + lenFraction = digitCount - exp - 1; + + } else { + // For a non-negative exponent, it's a question of placing the decimal point. + int w = exp + 1; + if (w < digitCount) { + // There are w whole-part digits + result.append(digits.subSequence(0, w)); + lenWhole = w; + result.append('.').append(digits.subSequence(w, digitCount)); + lenPoint = 1; + lenFraction = digitCount - w; + } else { + // All the digits are whole-part digits. + result.append(digits); + // Just occasionally (in r-format) we need more digits than the precision. + while (digitCount < w) { + result.append('0'); + digitCount += 1; + } + lenWhole = digitCount; + } + + if (noTruncate) { + // Extend the fraction as BigDecimal will have economised on zeros. + appendPointAndTrailingZeros(precision - digitCount); + } + } + + // Finally, ensure we have all and only the fractional digits we should. + if (!noTruncate) { + if (lenFraction < minFracDigits) { + // Otherwise, it should be at least the stated minimum length. + appendTrailingZeros(minFracDigits); + } else { + // And no more + removeTrailingZeros(minFracDigits); + } + } + } + + /** + * Common code for g-format, none-format and r-format used when the exponent is such that an + * exponential presentation is chosen. Normally the method removes trailing digits so as to + * shorten the presentation without loss of significance. Although no minimum number of + * fractional digits is enforced in the exponential presentation, when + * minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros + * generated in the conversion are not removed. This supports "%#g" format. + * + * @param digits from converting the value at a given precision. + * @param exp would be the exponent (in e-format), used to position the decimal point. + */ + private void appendExponential(CharSequence digits, int exp) { + + // The whole-part is the first digit. + result.append(digits.charAt(0)); + lenWhole = 1; + + // And the rest of the digits form the fractional part + int digitCount = digits.length(); + result.append('.').append(digits.subSequence(1, digitCount)); + lenPoint = 1; + lenFraction = digitCount - 1; + + // In no-truncate mode, the fraction is full precision. Otherwise trim it. + if (minFracDigits >= 0) { + // Note minFracDigits only applies to fixed formats. + removeTrailingZeros(0); + } + + // Finally, append the exponent as e+nn. + appendExponent(exp); + } + + /** + * Convert a double to digits and an exponent for use in float.__repr__ (or + * r-format). This method takes advantage of (or assumes) a close correspondence between + * {@link Double#toString(double)} and Python float.__repr__. The correspondence + * appears to be exact, insofar as the Java method produces the minimal non-zero digit string. + * It mostly chooses the same number of digits (and the same digits) as the CPython repr, but in + * a few cases Double.toString produces more digits. This method truncates to the + * number maxDigits, which in practice is always 17. + * + * @param value to convert + * @param maxDigits maximum number of digits to return in buf. + * @param buf for digits of result (recommend size be 20) + * @return the exponent + */ + private static int reprDigits(double value, int maxDigits, StringBuilder buf) { + + // Most of the work is done by Double. + String s = Double.toString(value); + + // Variables for scanning the string + int p = 0, end = s.length(), first = 0, point = end, exp; + char c = 0; + boolean allZero = true; + + // Scan whole part and fractional part digits + while (p < end) { + c = s.charAt(p++); + if (Character.isDigit(c)) { + if (allZero) { + if (c != '0') { + // This is the first non-zero digit. + buf.append(c); + allZero = false; + // p is one *after* the first non-zero digit. + first = p; + } + // Only seen zeros so far: do nothing. + } else { + // We've started, so every digit counts. + buf.append(c); + } + + } else if (c == '.') { + // We remember this location (one *after* '.') to calculate the exponent later. + point = p; + + } else { + // Something after the mantissa. (c=='E' we hope.) + break; + } + } + + // Possibly followed by an exponent. p has already advanced past the 'E'. + if (p < end && c == 'E') { + // If there is an exponent, the mantissa must be in standard form: m.mmmm + assert point == first + 1; + exp = Integer.parseInt(s.substring(p)); + + } else { + // Exponent is based on relationship of decimal point and first non-zero digit. + exp = point - first - 1; + // But that's only correct when the point is to the right (or absent). + if (exp < 0) { + // The point is to the left of the first digit + exp += 1; // = -(first-point) + } + } + + /* + * XXX This still does not round in all the cases it could. I think Java stops generating + * digits when the residual is <= ulp/2. This is to neglect the possibility that the extra + * ulp/2 (before it becomes a different double) could take us to a rounder numeral. To fix + * this, we could express ulp/2 as digits in the same scale as those in the buffer, and + * consider adding them. But Java's behaviour here is probably a manifestation of bug + * JDK-4511638. + */ + + // Sometimes the result is more digits than we want for repr. + if (buf.length() > maxDigits) { + // Chop the trailing digits, remembering the most significant lost digit. + int d = buf.charAt(maxDigits); + buf.setLength(maxDigits); + // We round half up. Not absolutely correct since Double has already rounded. + if (d >= '5') { + // Treat this as a "carry one" into the numeral buf[0:maxDigits]. + for (p = maxDigits - 1; p >= 0; p--) { + // Each pass of the loop does one carry from buf[p+1] to buf[p]. + d = buf.charAt(p) + 1; + if (d <= '9') { + // Carry propagation stops here. + buf.setCharAt(p, (char)d); + break; + } else { + // 9 + 1 -> 0 carry 1. Keep looping. + buf.setCharAt(p, '0'); + } + } + if (p < 0) { + /* + * We fell off the bottom of the buffer with one carry still to propagate. You + * may expect: buf.insert(0, '1') here, but note that every digit in + * buf[0:maxDigits] is currently '0', so all we need is: + */ + buf.setCharAt(0, '1'); + exp += 1; + } + } + } + + return exp; + } + + /** + * Append the trailing fractional zeros, as required by certain formats, so that the total + * number of fractional digits is no less than specified. If minFracDigits<=0, + * the method leaves the {@link #result} buffer unchanged. + * + * @param minFracDigits smallest number of fractional digits on return + */ + private void appendTrailingZeros(int minFracDigits) { + + int f = lenFraction; + + if (minFracDigits > f) { + if (lenPoint == 0) { + // First need to add a decimal point. (Implies lenFraction=0.) + result.append('.'); + lenPoint = 1; + } + + // Now make up the required number of zeros. + for (; f < minFracDigits; f++) { + result.append('0'); + } + lenFraction = f; + } + } + + /** + * Append the trailing fractional zeros, as required by certain formats, so that the total + * number of fractional digits is no less than specified. If there is no decimal point + * originally (and therefore no fractional part), the method will add a decimal point, even if + * it adds no zeros. + * + * @param minFracDigits smallest number of fractional digits on return + */ + private void appendPointAndTrailingZeros(int minFracDigits) { + + if (lenPoint == 0) { + // First need to add a decimal point. (Implies lenFraction=0.) + result.append('.'); + lenPoint = 1; + } + + // Now make up the required number of zeros. + int f; + for (f = lenFraction; f < minFracDigits; f++) { + result.append('0'); + } + lenFraction = f; + } + + /** + * Remove trailing zeros from the fractional part, as required by certain formats, leaving at + * least the number of fractional digits specified. If the resultant number of fractional digits + * is zero, this method will also remove the trailing decimal point (if there is one). + * + * @param minFracDigits smallest number of fractional digits on return + */ + private void removeTrailingZeros(int minFracDigits) { + + int f = lenFraction; + + if (lenPoint > 0) { + // There's a decimal point at least, and there may be some fractional digits. + if (minFracDigits == 0 || f > minFracDigits) { + + int fracStart = result.length() - f; + for (; f > minFracDigits; --f) { + if (result.charAt(fracStart - 1 + f) != '0') { + // Keeping this one as it isn't a zero + break; + } + } + + // f is now the number of fractional digits we wish to retain. + if (f == 0 && lenPoint > 0) { + // We will be stripping all the fractional digits. Take the decimal point too. + lenPoint = lenFraction = 0; + f = -1; + } else { + lenFraction = f; + } + + // Snip the characters we are going to remove (if any). + if (fracStart + f < result.length()) { + result.setLength(fracStart + f); + } + } + } + } + + /** + * Append the current value of {@code exp} in the format "e{:+02d}" (for example + * e+05, e-10, e+308 , etc.). + * + * @param exp exponent value to append + */ + private void appendExponent(int exp) { + + int marker = result.length(); + String e; + + // Deal with sign and leading-zero convention by explicit tests. + if (exp < 0) { + e = (exp <= -10) ? "e-" : "e-0"; + exp = -exp; + } else { + e = (exp < 10) ? "e+0" : "e+"; + } + + result.append(e).append(exp); + lenMarker = 1; + lenExponent = result.length() - marker - 1; + } + + /** + * Return the index in {@link #result} of the first letter. helper for {@link #uppercase()} and + * {@link #getExponent()} + */ + private int indexOfMarker() { + return start + lenSign + lenWhole + lenPoint + lenFraction; + } + +} diff --git a/src/org/python/core/stringlib/Formatter.java b/src/org/python/core/stringlib/Formatter.java deleted file mode 100644 --- a/src/org/python/core/stringlib/Formatter.java +++ /dev/null @@ -1,422 +0,0 @@ -package org.python.core.stringlib; - -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; - -import org.python.core.Py; -import org.python.core.util.ExtraMath; - -/** - * This class provides an approximate equivalent to corresponding parts of CPython's - * "~/Objects/stringlib/formatter.h", by concentrating in one place the formatting capabilities of - * built-in numeric types float and complex. - */ -public class Formatter { - - /** - * Format a floating-point value according to a conversion specification (created by - * {@link InternalFormatSpecParser#parse()}), the type of which must be one of - * {efgEFG%}, including padding to width. - * - * @param value to convert - * @param spec for a floating-point conversion - * @return formatted result - */ - public static String formatFloat(double value, InternalFormatSpec spec) { - InternalFormatter f = new InternalFormatter(spec); - String string = f.format(value); - return spec.pad(string, '>', 0); - } - - /** - * Format a complex value according to a conversion specification (created by - * {@link InternalFormatSpecParser#parse()}), the type of which must be one of - * {efgEFG}, including padding to width. The operation is effectively the - * application of the floating-point format to the real an imaginary parts, then the addition of - * padding once. - * - * @param value to convert - * @param spec for a floating-point conversion - * @return formatted result - */ - public static String formatComplex(double real, double imag, InternalFormatSpec spec) { - String string; - InternalFormatter f = new InternalFormatter(spec); - String r = f.format(real); - String i = f.format(imag); - if (i.charAt(0) == '-') { - string = r + i + "j"; - } else { - string = r + "+" + i + "j"; - } - return spec.pad(string, '>', 0); - } -} - - -/** - * A class that provides the implementation of floating-point formatting, and holds a conversion - * specification (created by {@link InternalFormatSpecParser#parse()}), a derived precision, and the - * sign of the number being converted. - */ -// Adapted from PyString's StringFormatter class. -final class InternalFormatter { - - InternalFormatSpec spec; - boolean negative; - int precision; - - /** - * Construct the formatter from a specification: default missing {@link #precision} to 6. - * - * @param spec parsed conversion specification - */ - public InternalFormatter(InternalFormatSpec spec) { - this.spec = spec; - this.precision = spec.precision; - if (this.precision == -1) { - this.precision = 6; - } - } - - /** - * If {@link #precision} exceeds an implementation limit, raise {@link Py#OverflowError}. - * - * @param type to name as the type being formatted - */ - private void checkPrecision(String type) { - if (precision > 250) { - // A magic number. Larger than in CPython. - throw Py.OverflowError("formatted " + type + " is too long (precision too long?)"); - } - - } - - /** - * Format abs(e) (in the given radix) with zero-padding to 2 decimal places, and - * store sgn(e) in {@link #negative}. - * - * @param e to convert - * @param radix in which to express - * @return string value of abs(e) base radix. - */ - private String formatExp(long e, int radix) { - checkPrecision("integer"); - if (e < 0) { - negative = true; - e = -e; - } - String s = Long.toString(e, radix); - while (s.length() < 2) { - s = "0" + s; - } - return s; - } - - /** - * Holds in its {@link #template} member, a {@link DecimalFormat} initialised for fixed point - * float formatting. - */ - static class DecimalFormatTemplate { - - static DecimalFormat template; - static { - template = - new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US)); - DecimalFormatSymbols symbols = template.getDecimalFormatSymbols(); - symbols.setNaN("nan"); - symbols.setInfinity("inf"); - template.setDecimalFormatSymbols(symbols); - template.setGroupingUsed(false); - } - } - - /** - * Return a copy of the pre-configured {@link DecimalFormatTemplate#template}, which may be - * further customised by the client. - * - * @return the template - */ - private static final DecimalFormat getDecimalFormat() { - return (DecimalFormat)DecimalFormatTemplate.template.clone(); - } - - /** - * Holds in its {@link #template} member, a {@link DecimalFormat} initialised for fixed point - * float formatting with percentage scaling and furniture. - */ - static class PercentageFormatTemplate { - - static DecimalFormat template; - static { - template = - new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US)); - DecimalFormatSymbols symbols = template.getDecimalFormatSymbols(); - symbols.setNaN("nan"); - symbols.setInfinity("inf"); - template.setDecimalFormatSymbols(symbols); - template.setGroupingUsed(false); - } - } - - /** - * Return a copy of the pre-configured {@link PercentageFormatTemplate#template}, which may be - * further customised by the client. - * - * @return the template - */ - private static final DecimalFormat getPercentageFormat() { - return (DecimalFormat)PercentageFormatTemplate.template.clone(); - } - - /** - * Format abs(v) in '{f}' format to {@link #precision} (places after - * decimal point), and store sgn(v) in {@link #negative}. Truncation is provided - * for that will remove trailing zeros and the decimal point (e.g. 1.200 becomes - * 1.2, and 4.000 becomes 4. This treatment is to support - * '{g}' format. (Also potentially '%g' format.) Truncation is not - * used (cannot validly be specified) for '{f}' format. - * - * @param v to convert - * @param truncate if true strip trailing zeros and decimal point - * @return converted value - */ - private String formatFloatDecimal(double v, boolean truncate) { - - checkPrecision("decimal"); - - // Separate the sign from v - if (v < 0) { - v = -v; - negative = true; - } - - // Configure a DecimalFormat: express truncation via minimumFractionDigits - DecimalFormat decimalFormat = getDecimalFormat(); - decimalFormat.setMaximumFractionDigits(precision); - decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision); - - // The DecimalFormat is already configured to group by comma at group size 3. - if (spec.thousands_separators) { - decimalFormat.setGroupingUsed(true); - } - - String ret = decimalFormat.format(v); - return ret; - } - - /** - * Format 100*abs(v) to {@link #precision} (places after decimal point), with a '%' - * (percent) sign following, and store sgn(v) in {@link #negative}. - * - * @param v to convert - * @param truncate if true strip trailing zeros - * @return converted value - */ - private String formatPercentage(double v, boolean truncate) { - - checkPrecision("decimal"); - - // Separate the sign from v - if (v < 0) { - v = -v; - negative = true; - } - - // Configure a DecimalFormat: express truncation via minimumFractionDigits - // XXX but truncation cannot be specified with % format! - DecimalFormat decimalFormat = getPercentageFormat(); - decimalFormat.setMaximumFractionDigits(precision); - decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision); - - String ret = decimalFormat.format(v); - return ret; - } - - /** - * Format abs(v) in '{e}' format to {@link #precision} (places after - * decimal point), and store sgn(v) in {@link #negative}. Truncation is provided - * for that will remove trailing zeros and the decimal point before the exponential part (e.g. - * 1.200e+04 becomes 1.2e+04, and 4.000e+05 becomes - * 4e+05. This treatment is to support '{g}' format. (Also potentially - * '%g' format.) Truncation is not used (cannot validly be specified) for - * '{e}' format. - * - * @param v to convert - * @param truncate if true strip trailing zeros and decimal point - * @return converted value - */ - private String formatFloatExponential(double v, char e, boolean truncate) { - - // Separate the sign from v - boolean isNegative = false; - if (v < 0) { - v = -v; - isNegative = true; - } - - /* - * Separate treatment is given to the exponent (I think) because java.text.DecimalFormat - * will insert a sign in a positive exponent, as in 1.234e+45 where Java writes 1.234E45. - */ - - // Power of 10 that will be the exponent. - double power = 0.0; - if (v > 0) { - // That is, if not zero (or NaN) - power = ExtraMath.closeFloor(Math.log10(v)); - } - - // Get exponent (as text) - String exp = formatExp((long)power, 10); - if (negative) { - // This is the sign of the power-of-ten *exponent* - negative = false; - exp = '-' + exp; - } else { - exp = '+' + exp; - } - - // Format the mantissa as a fixed point number - double base = v / Math.pow(10, power); - StringBuilder buf = new StringBuilder(); - buf.append(formatFloatDecimal(base, truncate)); - buf.append(e); - - buf.append(exp); - negative = isNegative; - - return buf.toString(); - } - - /** - * Format a floating-point number according to the specification represented by this - * InternalFormatter. The conversion type, precision, and flags for grouping or - * percentage are dealt with here. At the point this is used, we know the {@link #spec} has type - * in {efgEFG}. - * - * @param value to convert - * @return formatted version - */ - @SuppressWarnings("fallthrough") - public String format(double value) { - - // XXX Possible duplication in handling NaN and upper/lower case here when methiods - // floatFormatDecimal, formatFloatExponential, etc. appear to do those things. - - String string; // return value - - if (spec.alternate) { - // XXX in %g, but not {:g} alternate form means always include a decimal point - throw Py.ValueError("Alternate form (#) not allowed in float format specifier"); - } - - int sign = Double.compare(value, 0.0d); - - if (Double.isNaN(value)) { - // Express NaN cased according to the conversion type. - if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') { - string = "NAN"; - } else { - string = "nan"; - } - - } else if (Double.isInfinite(value)) { - // Express signed infinity cased according to the conversion type. - if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') { - if (value > 0) { - string = "INF"; - } else { - string = "-INF"; - } - } else { - if (value > 0) { - string = "inf"; - } else { - string = "-inf"; - } - } - - } else { - - switch (spec.type) { - case 'e': - case 'E': - // Exponential case: 1.23e-45 - string = formatFloatExponential(value, spec.type, false); - if (spec.type == 'E') { - string = string.toUpperCase(); - } - break; - - case 'f': - case 'F': - // Fixed case: 123.45 - string = formatFloatDecimal(value, false); - if (spec.type == 'F') { - string = string.toUpperCase(); - } - break; - - case 'g': - case 'G': - // Mixed "general" case: e or f format according to exponent. - // XXX technique not wholly effective, for example on 0.0000999999999999995. - int exponent = - (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value))); - int origPrecision = precision; - /* - * (Python docs) Suppose formatting with presentation type 'e' and precision p-1 - * would give exponent exp. Then if -4 <= exp < p, ... - */ - if (exponent >= -4 && exponent < precision) { - /* - * ... the number is formatted with presentation type 'f' and precision - * p-1-exp. - */ - precision -= exponent + 1; - string = formatFloatDecimal(value, !spec.alternate); - } else { - /* - * ... Otherwise, the number is formatted with presentation type 'e' and - * precision p-1. - */ - precision--; - string = - formatFloatExponential(value, (char)(spec.type - 2), - !spec.alternate); - } - if (spec.type == 'G') { - string = string.toUpperCase(); - } - precision = origPrecision; - break; - - case '%': - // Multiplies by 100 and displays in f-format, followed by a percent sign. - string = formatPercentage(value, false); - break; - - default: - // Should never get here, since this was checked in PyFloat. - throw Py.ValueError(String.format( - "Unknown format code '%c' for object of type 'float'", spec.type)); - } - } - - // If positive, deal with mandatory sign, or mandatory space. - if (sign >= 0) { - if (spec.sign == '+') { - string = "+" + string; - } else if (spec.sign == ' ') { - string = " " + string; - } - } - - // If negative, insert a minus sign where needed, and we haven't already (e.g. "-inf"). - if (sign < 0 && string.charAt(0) != '-') { - string = "-" + string; - } - return string; - } -} diff --git a/src/org/python/core/stringlib/InternalFormat.java b/src/org/python/core/stringlib/InternalFormat.java new file mode 100644 --- /dev/null +++ b/src/org/python/core/stringlib/InternalFormat.java @@ -0,0 +1,824 @@ +// Copyright (c) Jython Developers +package org.python.core.stringlib; + +import org.python.core.Py; +import org.python.core.PyException; + +public class InternalFormat { + + /** + * Create a {@link Spec} object by parsing a format specification. + * + * @param text to parse + * @return parsed equivalent to text + */ + public static Spec fromText(String text) { + Parser parser = new Parser(text); + return parser.parse(); + } + + /** + * A class that provides the base for implementations of type-specific formatting. In a limited + * way, it acts like a StringBuilder to which text and one or more numbers may be appended, + * formatted according to the format specifier supplied at construction. These are ephemeral + * objects that are not, on their own, thread safe. + */ + public static class Formatter implements Appendable { + + /** The specification according to which we format any number supplied to the method. */ + protected final Spec spec; + /** The (partial) result. */ + protected StringBuilder result; + + /** The number we are working on floats at the end of the result, and starts here. */ + protected int start; + /** If it contains no sign, this length is zero, and 1 otherwise. */ + protected int lenSign; + /** The length of the whole part (to left of the decimal point or exponent) */ + protected int lenWhole; + + /** + * Construct the formatter from a specification and initial buffer capacity. A reference is + * held to this specification, but it will not be modified by the actions of this class. + * + * @param spec parsed conversion specification + * @param width of buffer initially + */ + public Formatter(Spec spec, int width) { + this.spec = spec; + result = new StringBuilder(width); + } + + /** + * Current (possibly final) result of the formatting, as a String. + * + * @return formatted result + */ + public String getResult() { + return result.toString(); + } + + /* + * Implement Appendable interface by delegation to the result buffer. + * + * @see java.lang.Appendable#append(char) + */ + @Override + public Formatter append(char c) { + result.append(c); + return this; + } + + @Override + public Formatter append(CharSequence csq) { + result.append(csq); + return this; + } + + @Override + public Formatter append(CharSequence csq, int start, int end) // + throws IndexOutOfBoundsException { + result.append(csq, start, end); + return this; + } + + /** + * Clear the instance variables describing the latest object in {@link #result}, ready to + * receive a new number + */ + public void setStart() { + // Mark the end of the buffer as the start of the current object and reset all. + start = result.length(); + // Clear the variable describing the latest number in result. + reset(); + } + + /** + * Clear the instance variables describing the latest object in {@link #result}, ready to + * receive a new one. + */ + protected void reset() { + // Clear the variable describing the latest object in result. + lenSign = lenWhole = 0; + } + + /** + * Supports {@link #toString()} by returning the lengths of the successive sections in the + * result buffer, used for navigation relative to {@link #start}. The toString + * method shows a '|' character between each section when it prints out the buffer. Override + * this when you define more lengths in the subclass. + * + * @return + */ + protected int[] sectionLengths() { + return new int[] {lenSign, lenWhole}; + } + + /** + * {@inheritDoc} + *

+ * Overridden to provide a debugging view in which the actual text is shown divided up by + * the len* member variables. If the dividers don't look right, those variables + * have not remained consistent with the text. + */ + @Override + public String toString() { + if (result == null) { + return ("[]"); + } else { + StringBuilder buf = new StringBuilder(result.length() + 20); + buf.append(result); + try { + int p = start; + buf.insert(p++, '['); + for (int len : sectionLengths()) { + p += len; + buf.insert(p++, '|'); + } + buf.setCharAt(p - 1, ']'); + } catch (IndexOutOfBoundsException e) { + // Some length took us beyond the end of the result buffer. Pass. + } + return buf.toString(); + } + } + + /** + * Insert grouping characters (conventionally commas) into the whole part of the number. + * {@link #lenWhole} will increase correspondingly. + * + * @param groupSize normally 3. + * @param comma or some other character to use as a separator. + */ + protected void groupDigits(int groupSize, char comma) { + + // Work out how many commas (or whatever) it takes to group the whole-number part. + int commasNeeded = (lenWhole - 1) / groupSize; + + if (commasNeeded > 0) { + // Index *just after* the current last digit of the whole part of the number. + int from = start + lenSign + lenWhole; + // Open a space into which the whole part will expand. + makeSpaceAt(from, commasNeeded); + // Index *just after* the end of that space. + int to = from + commasNeeded; + // The whole part will be longer by the number of commas to be inserted. + lenWhole += commasNeeded; + + /* + * Now working from high to low, copy all the digits that have to move. Each pass + * copies one group and inserts a comma, which makes the to-pointer move one place + * extra. The to-pointer descends upon the from-pointer from the right. + */ + while (to > from) { + // Copy a group + for (int i = 0; i < groupSize; i++) { + result.setCharAt(--to, result.charAt(--from)); + } + // Write the comma that precedes it. + result.setCharAt(--to, comma); + } + } + } + + /** + * Make a space in {@link #result} of a certain size and position. On return, the segment + * lengths are likely to be invalid until the caller adjusts them corresponding to the + * insertion. There is no guarantee what the opened space contains. + * + * @param pos at which to make the space + * @param size of the space + */ + protected void makeSpaceAt(int pos, int size) { + int n = result.length(); + if (pos < n) { + // Space is not at the end: must copy what's to the right of pos. + String tail = result.substring(pos); + result.setLength(n + size); + result.replace(pos + size, n + size, tail); + } else { + // Space is at the end. + result.setLength(n + size); + } + } + + /** + * Convert letters in the representation of the current number (in {@link #result}) to upper + * case. + */ + protected void uppercase() { + int end = result.length(); + for (int i = start; i < end; i++) { + char c = result.charAt(i); + result.setCharAt(i, Character.toUpperCase(c)); + } + } + + /** + * Pad the result so far (defined as the entire contents of {@link #result}) using the + * alignment, target width and fill character defined in {@link #spec}. The action of + * padding will increase the overall length of the result to the target width, if that is + * greater than the current length. + *

+ * When the padding method has decided that that it needs to add n padding characters, it + * will affect {@link #start} or {@link #lenSign} as follows. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
alignmeaningstartlenSignresult.length()
<left-aligned+0+0+n
>right-aligned+n+0+n
^centred+(n/2)+0+n
=pad after sign+0+n+n
+ * Note that we may have converted more than one value into the result buffer (for example + * when formatting a complex number). The pointer start is at the start of the + * last number converted. Padding with zeros, and the "pad after sign" mode, will produce a + * result you probably don't want. It is up to the client to disallow this (which + * complex does). + * + * @param value to pad + * @return this object + */ + public Formatter pad() { + + // We'll need this many pad characters (if>0). Note Spec.UNDEFINED<0. + int n = spec.width - result.length(); + if (n > 0) { + + char align = spec.getAlign('>'); // Right for numbers (wrong for strings) + char fill = spec.getFill(' '); + + // Start by assuming padding is all leading ('>' case or '=') + int leading = n; + + // Split the total padding according to the alignment + if (align == '^') { + // Half the padding before + leading = n / 2; + } else if (align == '<') { + // All the padding after + leading = 0; + } + + // All padding that is not leading is trailing + int trailing = n - leading; + + // Insert the leading space + if (leading > 0) { + int pos; + if (align == '=') { + // Incorporate into the (latest) whole part + pos = start + lenSign; + lenWhole += leading; + } else { + // Insert at the very beginning (not start) by default. + pos = 0; + start += leading; + } + makeSpaceAt(pos, leading); + for (int i = 0; i < leading; i++) { + result.setCharAt(pos + i, fill); + } + } + + // Append the trailing space + for (int i = 0; i < trailing; i++) { + result.append(fill); + } + + // Check for special case + if (align == '=' && fill == '0' && spec.grouping) { + // We must extend the grouping separator into the padding + zeroPadAfterSignWithGroupingFixup(3, ','); + } + } + + return this; + } + + /** + * Fix-up the zero-padding of the last formatted number in {@link #result()} in the special + * case where a sign-aware padding ({@link #spec}.align='=') was requested, the + * fill character is '0', and the digits are to be grouped. In these exact + * circumstances, the grouping, which must already have been applied to the (whole part) + * number itself, has to be extended into the zero-padding. + * + *

+         * >>> format(-12e8, " =30,.3f")
+         * '-            1,200,000,000.000'
+         * >>> format(-12e8, "*=30,.3f")
+         * '-************1,200,000,000.000'
+         * >>> format(-12e8, "*>30,.3f")
+         * '************-1,200,000,000.000'
+         * >>> format(-12e8, "0>30,.3f")
+         * '000000000000-1,200,000,000.000'
+         * >>> format(-12e8, "0=30,.3f")
+         * '-0,000,000,001,200,000,000.000'
+         * 
+ * + * The padding has increased the overall length of the result to the target width. About one + * in three call to this method adds one to the width, because the whole part cannot start + * with a comma. + * + *
+         * >>> format(-12e8, " =30,.4f")
+         * '-           1,200,000,000.0000'
+         * >>> format(-12e8, "0=30,.4f")
+         * '-0,000,000,001,200,000,000.0000'
+         * 
+ * + * Insert grouping characters (conventionally commas) into the whole part of the number. + * {@link #lenWhole} will increase correspondingly. + * + * @param groupSize normally 3. + * @param comma or some other character to use as a separator. + */ + protected void zeroPadAfterSignWithGroupingFixup(int groupSize, char comma) { + /* + * Suppose the format call was format(-12e8, "0=30,.3f"). At this point, we have + * something like this in result: .. [-|0000000000001,200,000,000|.|000||] + * + * All we need do is over-write some of the zeros with the separator comma, in the + * portion marked as the whole-part: [-|0,000,000,001,200,000,000|.|000||] + */ + + // First digit of the whole-part. + int firstZero = start + lenSign; + // One beyond last digit of the whole-part. + int p = firstZero + lenWhole; + // Step back down the result array visiting the commas. (Easiest to do all of them.) + int step = groupSize + 1; + for (p = p - step; p >= firstZero; p -= step) { + result.setCharAt(p, comma); + } + + // Sometimes the last write was exactly at the first padding zero. + if (p + step == firstZero) { + /* + * Suppose the format call was format(-12e8, "0=30,.4f"). At the beginning, we had + * something like this in result: . [-|000000000001,200,000,000|.|0000||] + * + * And now, result looks like this: [-|0000,000,001,200,000,000|.|0000||] in which + * the first zero is wrong as it stands, nor can it just be over-written with a + * comma. We have to insert another zero, even though this makes the result longer + * than we were given. + */ + result.insert(firstZero, '0'); + lenWhole += 1; + } + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting: + *

+ * "Unknown format code '"+code+"' for object of type '"+forType+"'" + * + * @param code the presentation type + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException unknownFormat(char code, String forType) { + String msg = "Unknown format code '" + code + "' for object of type '" + forType + "'"; + return Py.ValueError(msg); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that alternate form is not + * allowed in a format specifier for the named type. + * + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException alternateFormNotAllowed(String forType) { + return notAllowed("Alternate form (#)", forType); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that the given alignment + * flag is not allowed in a format specifier for the named type. + * + * @param align type of alignment + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException alignmentNotAllowed(char align, String forType) { + return notAllowed("'" + align + "' alignment flag", forType); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that zero padding is not + * allowed in a format specifier for the named type. + * + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException zeroPaddingNotAllowed(String forType) { + return notAllowed("Zero padding", forType); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that some format specifier + * feature is not allowed for the named type. + * + * @param particularOutrage committed in the present case + * @param forType the type it where it is an outrage + * @return exception to throw + */ + protected static PyException notAllowed(String particularOutrage, String forType) { + String msg = particularOutrage + " is not allowed in " + forType + " format specifier"; + return Py.ValueError(msg); + } + + } + + /** + * Parsed PEP-3101 format specification of a single field, encapsulating the format for use by + * formatting methods. This class holds the several attributes that might be decoded from a + * format specifier. Each attribute has a reserved value used to indicate "unspecified". + * Spec objects may be merged such that one Spec provides values, + * during the construction of a new Spec, for attributes that are unspecified in a + * primary source. + *

+ * This structure is returned by factory method {@link #fromText(CharSequence)}, and having + * public final members is freely accessed by formatters such as {@link FloatBuilder}, and the + * __format__ methods of client object types. + *

+ * The fields correspond to the elements of a format specification. The grammar of a format + * specification is: + * + *

+     * [[fill]align][sign][#][0][width][,][.precision][type]
+     * 
+ * + * A typical idiom is: + * + *
+     *     private static final InternalFormatSpec FLOAT_DEFAULT = InternalFormatSpec.from(">");
+     *     ...
+     *         InternalFormatSpec spec = InternalFormatSpec.from(specString, FLOAT_DEFAULT);
+     *         ... // Validation of spec.type, and other attributes, for this type.
+     *         FloatBuilder buf = new FloatBuilder(spec);
+     *         buf.format(value);
+     *         String result = buf.getResult();
+     *
+     * 
+ */ + public static class Spec { + + /** The fill character specified, or '\uffff' if unspecified. */ + public final char fill; + /** + * Alignment indicator is one of {'<', '^', '>', '=', or '\uffff' if + * unspecified. + */ + public final char align; + /** + * Sign-handling flag, one of '+', '-', or ' ', or + * '\uffff' if unspecified. + */ + public final char sign; + /** The alternative format flag '#' was given. */ + public final boolean alternate; + /** Width to which to pad the result, or -1 if unspecified. */ + public final int width; + /** Insert the grouping separator (which in Python always indicates a group-size of 3). */ + public final boolean grouping; + /** Precision decoded from the format, or -1 if unspecified. */ + public final int precision; + /** Type key from the format, or '\uffff' if unspecified. */ + public final char type; + + /** Non-character code point used to represent "no value" in char attributes. */ + public static final char NONE = '\uffff'; + /** Negative value used to represent "no value" in int attributes. */ + public static final int UNSPECIFIED = -1; + + /** + * Test to see if an attribute has been specified. + * + * @param c attribute + * @return true only if the attribute is not equal to {@link #NONE} + */ + public static final boolean specified(char c) { + return c != NONE; + } + + /** + * Test to see if an attribute has been specified. + * + * @param value of attribute + * @return true only if the attribute is >=0 (meaning that it has been specified). + */ + public static final boolean specified(int value) { + return value >= 0; + } + + /** + * Constructor to set all the fields in the format specifier. + * + *
+         * [[fill]align][sign][#][0][width][,][.precision][type]
+         * 
+ * + * @param fill fill character (or {@link #NONE} + * @param align alignment indicator, one of {'<', '^', '>', '=' + * @param sign policy, one of '+', '-', or ' '. + * @param alternate true to request alternate formatting mode ('#' flag). + * @param width of field after padding or -1 to default + * @param grouping true to request comma-separated groups + * @param precision (e.g. decimal places) or -1 to default + * @param type indicator character + */ + public Spec(char fill, char align, char sign, boolean alternate, int width, + boolean grouping, int precision, char type) { + this.fill = fill; + this.align = align; + this.sign = sign; + this.alternate = alternate; + this.width = width; + this.grouping = grouping; + this.precision = precision; + this.type = type; + } + + /** + * Return a format specifier (text) equivalent to the value of this Spec. + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + if (specified(fill)) { + buf.append(fill); + } + if (specified(align)) { + buf.append(align); + } + if (specified(sign)) { + buf.append(sign); + } + if (alternate) { + buf.append('#'); + } + if (specified(width)) { + buf.append(width); + } + if (grouping) { + buf.append(','); + } + if (specified(precision)) { + buf.append('.').append(precision); + } + if (specified(type)) { + buf.append(type); + } + return buf.toString(); + } + + /** + * Return a merged Spec object, in which any attribute of this object, that is + * specified (or true) has the same value in the result, and any attribute of + * this object that is unspecified (or false) has the value that attribute + * takes in the other object. This the second object supplies default values. (These + * defaults may also be unspecified.) The use of this method is to allow a Spec + * constructed from text to record exactly, and only, what was in the textual specification, + * while the __format__ method of a client object supplies its type-specific defaults. Thus + * "20" means "<20s" to a str, ">20.12" to a float and ">20.12g" + * to a complex. + * + * @param defaults to merge where this object does not specify the attribute. + * @return a new Spec object. + */ + public Spec withDefaults(Spec other) { + return new Spec(// + specified(fill) ? fill : other.fill, // + specified(align) ? align : other.align, // + specified(sign) ? sign : other.sign, // + alternate || other.alternate, // + specified(width) ? width : other.width, // + grouping || other.grouping, // + specified(precision) ? precision : other.precision, // + specified(type) ? type : other.type // + ); + } + + /** + * Defaults applicable to most numeric types. Equivalent to " >" + */ + public static final Spec NUMERIC = new Spec(' ', '>', Spec.NONE, false, Spec.UNSPECIFIED, + false, Spec.UNSPECIFIED, Spec.NONE); + + /** + * Constructor offering just precision and type. + * + *
+         * [.precision][type]
+         * 
+ * + * @param precision (e.g. decimal places) + * @param type indicator character + */ + public Spec(int width, int precision, char type) { + this(' ', '>', Spec.NONE, false, UNSPECIFIED, false, precision, type); + } + + /** The alignment from the parsed format specification, or default. */ + public char getFill(char defaultFill) { + return specified(fill) ? fill : defaultFill; + } + + /** The alignment from the parsed format specification, or default. */ + public char getAlign(char defaultAlign) { + return specified(align) ? align : defaultAlign; + } + + /** The precision from the parsed format specification, or default. */ + public int getPrecision(int defaultPrecision) { + return specified(precision) ? precision : defaultPrecision; + } + + /** The type code from the parsed format specification, or default supplied. */ + public char getType(char defaultType) { + return specified(type) ? type : defaultType; + } + + } + + /** + * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} + * method that translates the format specification into an Spec object. + */ + private static class Parser { + + private String spec; + private int ptr; + + /** + * Constructor simply holds the specification string ahead of the {@link #parse()} + * operation. + * + * @param spec format specifier to parse (e.g. "<+12.3f") + */ + Parser(String spec) { + this.spec = spec; + this.ptr = 0; + } + + /** + * Parse the specification with which this object was initialised into an {@link Spec}, + * which is an object encapsulating the format for use by formatting methods. This parser + * deals only with the format specifiers themselves, as accepted by the + * __format__ method of a type, or the format() built-in, not + * format strings in general as accepted by str.format(). + * + * @return the Spec equivalent to the string given. + */ + /* + * This method is the equivalent of CPython's parse_internal_render_format_spec() in + * ~/Objects/stringlib/formatter.h, but we deal with defaults another way. + */ + Spec parse() { + + char fill = Spec.NONE, align = Spec.NONE; + char sign = Spec.NONE, type = Spec.NONE; + boolean alternate = false, grouping = false; + int width = Spec.UNSPECIFIED, precision = Spec.UNSPECIFIED; + + // Scan [[fill]align] ... + if (isAlign()) { + // First is alignment. fill not specified. + align = spec.charAt(ptr++); + } else { + // Peek ahead + ptr += 1; + if (isAlign()) { + // Second character is alignment, so first is fill + fill = spec.charAt(0); + align = spec.charAt(ptr++); + } else { + // Second character is not alignment. We are still at square zero. + ptr = 0; + } + } + + // Scan [sign] ... + if (isAt("+- ")) { + sign = spec.charAt(ptr++); + } + + // Scan [#] ... + alternate = scanPast('#'); + + // Scan [0] ... + if (scanPast('0')) { + // Accept 0 here as equivalent to zero-fill but only not set already. + if (!Spec.specified(fill)) { + fill = '0'; + if (!Spec.specified(align)) { + // Also accept it as equivalent to "=" aligment but only not set already. + align = '='; + } + } + } + + // Scan [width] + if (isDigit()) { + width = scanInteger(); + } + + // Scan [,][.precision][type] + grouping = scanPast(','); + + // Scan [.precision] + if (scanPast('.')) { + if (isDigit()) { + precision = scanInteger(); + } else { + throw new IllegalArgumentException("Format specifier missing precision"); + } + } + + // Scan [type] + if (ptr < spec.length()) { + type = spec.charAt(ptr++); + } + + // If we haven't reached the end, something is wrong + if (ptr != spec.length()) { + throw new IllegalArgumentException("Invalid conversion specification"); + } + + // Restrict grouping to known formats. (Mirrors CPython, but misplaced?) + if (grouping && "defgEG%F\0".indexOf(type) == -1) { + throw new IllegalArgumentException("Cannot specify ',' with '" + type + "'."); + } + + // Create a specification + return new Spec(fill, align, sign, alternate, width, grouping, precision, type); + } + + /** Test that the next character is exactly the one specified, and advance past it if it is. */ + private boolean scanPast(char c) { + if (ptr < spec.length() && spec.charAt(ptr) == c) { + ptr++; + return true; + } else { + return false; + } + } + + /** Test that the next character is one of a specified set. */ + private boolean isAt(String chars) { + return ptr < spec.length() && (chars.indexOf(spec.charAt(ptr)) >= 0); + } + + /** Test that the next character is one of the alignment characters. */ + private boolean isAlign() { + return ptr < spec.length() && ("<^>=".indexOf(spec.charAt(ptr)) >= 0); + } + + /** Test that the next character is a digit. */ + private boolean isDigit() { + return ptr < spec.length() && Character.isDigit(spec.charAt(ptr)); + } + + /** The current character is a digit (maybe a sign). Scan the integer, */ + private int scanInteger() { + int p = ptr++; + while (isDigit()) { + ptr++; + } + return Integer.parseInt(spec.substring(p, ptr)); + } + + } + +} -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:11 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:11 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_built-in_round_and_floa?= =?utf-8?q?t=2E=5F=5Fint=5F=5F_to_correct_boundaries=2E?= Message-ID: <3gDbJq01SBz7LkF@mail.python.org> http://hg.python.org/jython/rev/1beea214e208 changeset: 7217:1beea214e208 user: Jeff Allen date: Tue Apr 22 08:36:14 2014 +0100 summary: Fix built-in round and float.__int__ to correct boundaries. This allows us to remove several skips from test_float. files: Lib/test/test_float.py | 14 +--- src/org/python/core/PyFloat.java | 12 ++- src/org/python/core/__builtin__.java | 21 +--- src/org/python/core/util/ExtraMath.java | 54 ++++++++++++- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -76,8 +76,6 @@ # Double check. self.assertEqual(type(int(n)), type(n)) - @unittest.skipIf(test_support.is_jython, - "FIXME: int boundries not right on Jython") def test_conversion_to_int(self): # Check that floats within the range of an int convert to type # int, not long. (issue #11144.) @@ -732,11 +730,9 @@ self.assertRaises(TypeError, round, INF, 0.0) self.assertRaises(TypeError, round, -INF, 1.0) - self.assertRaises(TypeError, round, NAN, "ceci n'est pas un integer") + self.assertRaises(TypeError, round, NAN, "ceci n'est pas un entier") self.assertRaises(TypeError, round, -0.0, 1j) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working in Jython") def test_large_n(self): for n in [324, 325, 400, 2**31-1, 2**31, 2**32, 2**100]: self.assertEqual(round(123.456, n), 123.456) @@ -749,8 +745,6 @@ self.assertEqual(round(1e150, 309), 1e150) self.assertEqual(round(1.4e-315, 315), 1e-315) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working in Jython") def test_small_n(self): for n in [-308, -309, -400, 1-2**31, -2**31, -2**31-1, -2**100]: self.assertEqual(round(123.456, n), 0.0) @@ -758,14 +752,10 @@ self.assertEqual(round(1e300, n), 0.0) self.assertEqual(round(1e-320, n), 0.0) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working in Jython") def test_overflow(self): self.assertRaises(OverflowError, round, 1.6e308, -308) self.assertRaises(OverflowError, round, -1.7e308, -308) - @unittest.skipIf(test_support.is_jython, - "FIXME: rounding incorrect in Jython") @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short', "test applies only when using short float repr style") def test_previous_round_bugs(self): @@ -775,8 +765,6 @@ self.assertEqual(round(56294995342131.5, 3), 56294995342131.5) - @unittest.skipIf(test_support.is_jython, - "FIXME: rounding incorrect in Jython") @unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short', "test applies only when using short float repr style") def test_halfway_cases(self): diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java --- a/src/org/python/core/PyFloat.java +++ b/src/org/python/core/PyFloat.java @@ -811,12 +811,18 @@ return float___int__(); } + /** Smallest value that cannot be represented as an int */ + private static double INT_LONG_BOUNDARY = -(double)Integer.MIN_VALUE; // 2^31 + @ExposedMethod(doc = BuiltinDocs.float___int___doc) final PyObject float___int__() { - if (getValue() <= Integer.MAX_VALUE && getValue() >= Integer.MIN_VALUE) { - return new PyInteger((int)getValue()); + double v = getValue(); + if (v < INT_LONG_BOUNDARY && v > -(INT_LONG_BOUNDARY + 1.0)) { + // v will fit into an int (when rounded towards zero). + return new PyInteger((int)v); + } else { + return __long__(); } - return __long__(); } @Override diff --git a/src/org/python/core/__builtin__.java b/src/org/python/core/__builtin__.java --- a/src/org/python/core/__builtin__.java +++ b/src/org/python/core/__builtin__.java @@ -15,6 +15,7 @@ import java.util.Map; import org.python.antlr.base.mod; +import org.python.core.util.ExtraMath; import org.python.core.util.RelativeFile; import org.python.core.util.StringUtil; import org.python.modules._functools._functools; @@ -1601,20 +1602,14 @@ ArgParser ap = new ArgParser("round", args, kwds, new String[] {"number", "ndigits"}, 0); PyObject number = ap.getPyObject(0); int ndigits = ap.getIndex(1, 0); - return round(number.asDouble(), ndigits); - } - - private static PyFloat round(double f, int digits) { - boolean neg = f < 0; - double multiple = Math.pow(10., digits); - if (neg) { - f = -f; + double x = number.asDouble(); + double r = ExtraMath.round(x, ndigits); + if (Double.isInfinite(r) && !Double.isInfinite(x)) { + // Rounding caused magnitude to increase beyond representable range + throw Py.OverflowError("rounded value too large to represent"); + } else { + return new PyFloat(r); } - double tmp = Math.floor(f * multiple + 0.5); - if (neg) { - tmp = -tmp; - } - return new PyFloat(tmp / multiple); } } diff --git a/src/org/python/core/util/ExtraMath.java b/src/org/python/core/util/ExtraMath.java --- a/src/org/python/core/util/ExtraMath.java +++ b/src/org/python/core/util/ExtraMath.java @@ -1,6 +1,9 @@ // Copyright (c) Corporation for National Research Initiatives package org.python.core.util; +import java.math.BigDecimal; +import java.math.RoundingMode; + /** * A static utility class with two additional math functions. */ @@ -26,11 +29,58 @@ } /** - * Returns floor(v) except when v is very close to the next number, when it - * returns ceil(v); + * Returns floor(v) except when v is very close to the next number, when it returns ceil(v); */ public static double closeFloor(double v) { double floor = Math.floor(v); return close(v, floor + 1.0) ? floor + 1.0 : floor; } + + /** + * Round the argument x to n decimal places. (Rounding is half-up in Python 2.) The method uses + * BigDecimal, to compute r(x*10n)*10-n, where r() round to + * the nearest integer. It takes some short-cuts for extreme values. + *

+ * For sufficiently small x*10n, the rounding is to zero, and the return value + * is a signed zero (same sign as x). Suppose x = a*2b, where the significand + * we must have a<2. Sufficiently small means such that n log210 < + * -(b+2). + *

+ * For sufficiently large x*10n, the adjustment of rounding is too small to + * affect the least significant bit. That is a*2b represents an amount greater + * than one, and rounding no longer affects the value, and the return is x. Since the matissa + * has 52 fractional bits, sufficiently large means such that n log210 > 52-b. + * + * @param x to round + * @param n decimal places + * @return x rounded. + */ + public static double round(double x, int n) { + + if (Double.isNaN(x) || Double.isInfinite(x) || x == 0.0) { + // nans, infinities and zeros round to themselves + return x; + + } else { + + // (Slightly less than) n*log2(10). + float nlog2_10 = 3.3219f * n; + + // x = a * 2^b and a<2. + int b = Math.getExponent(x); + + if (nlog2_10 > 52 - b) { + // When n*log2(10) > nmax, the lsb of abs(x) is >1, so x rounds to itself. + return x; + } else if (nlog2_10 < -(b + 2)) { + // When n*log2(10) < -(b+2), abs(x)<0.5*10^n so x rounds to (signed) zero. + return Math.copySign(0.0, x); + } else { + // We have to work it out properly. + BigDecimal xx = new BigDecimal(x); + BigDecimal rr = xx.setScale(n, RoundingMode.HALF_UP); + return rr.doubleValue(); + } + } + } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:13 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:13 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_float_formatting_work_to_trunk?= Message-ID: <3gDbJs2HjJz7Ljs@mail.python.org> http://hg.python.org/jython/rev/3fef65b876ec changeset: 7218:3fef65b876ec parent: 7211:e9c44964857e parent: 7217:1beea214e208 user: Jeff Allen date: Wed Apr 23 20:39:02 2014 +0100 summary: Merge float formatting work to trunk files: Lib/test/test_complex.py | 213 ++- Lib/test/test_float.py | 52 +- Lib/test/test_float_jy.py | 19 +- src/org/python/core/PyComplex.java | 203 +- src/org/python/core/PyFloat.java | 277 +- src/org/python/core/PyInteger.java | 93 +- src/org/python/core/PyString.java | 69 +- src/org/python/core/__builtin__.java | 21 +- src/org/python/core/stringlib/FloatFormatter.java | 880 ++++++++++ src/org/python/core/stringlib/Formatter.java | 247 -- src/org/python/core/stringlib/InternalFormat.java | 824 +++++++++ src/org/python/core/stringlib/InternalFormatSpec.java | 58 +- src/org/python/core/stringlib/InternalFormatSpecParser.java | 29 +- src/org/python/core/util/ExtraMath.java | 54 +- 14 files changed, 2443 insertions(+), 596 deletions(-) diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py --- a/Lib/test/test_complex.py +++ b/Lib/test/test_complex.py @@ -220,6 +220,7 @@ def test_conjugate(self): self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j) + @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") def test_constructor(self): class OS: def __init__(self, value): self.value = value @@ -264,6 +265,188 @@ self.assertAlmostEqual(complex("-1"), -1) self.assertAlmostEqual(complex("+1"), +1) #FIXME: these are not working in Jython. + self.assertAlmostEqual(complex("(1+2j)"), 1+2j) + self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j) + # ] + self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j) + #FIXME: these are not working in Jython. + self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j) + self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j) + self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j) + # ] + self.assertAlmostEqual(complex("J"), 1j) + #FIXME: this is not working in Jython. + self.assertAlmostEqual(complex("( j )"), 1j) + # ] + self.assertAlmostEqual(complex("+J"), 1j) + #FIXME: this is not working in Jython. + self.assertAlmostEqual(complex("( -j)"), -1j) + # ] + self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j) + self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j) + self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j) + + class complex2(complex): pass + self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j) + self.assertAlmostEqual(complex(real=17, imag=23), 17+23j) + self.assertAlmostEqual(complex(real=17+23j), 17+23j) + self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j) + self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j) + + # check that the sign of a zero in the real or imaginary part + # is preserved when constructing from two floats. (These checks + # are harmless on systems without support for signed zeros.) + def split_zeros(x): + """Function that produces different results for 0. and -0.""" + return atan2(x, -1.) + + self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.)) + #FIXME: this is not working in Jython. + self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.)) + # ] + self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.)) + self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.)) + + c = 3.14 + 1j + self.assertTrue(complex(c) is c) + del c + + self.assertRaises(TypeError, complex, "1", "1") + self.assertRaises(TypeError, complex, 1, "1") + + if test_support.have_unicode: + self.assertEqual(complex(unicode(" 3.14+J ")), 3.14+1j) + + # SF bug 543840: complex(string) accepts strings with \0 + # Fixed in 2.3. + self.assertRaises(ValueError, complex, '1+1j\0j') + + self.assertRaises(TypeError, int, 5+3j) + self.assertRaises(TypeError, long, 5+3j) + self.assertRaises(TypeError, float, 5+3j) + self.assertRaises(ValueError, complex, "") + self.assertRaises(TypeError, complex, None) + self.assertRaises(ValueError, complex, "\0") + self.assertRaises(ValueError, complex, "3\09") + self.assertRaises(TypeError, complex, "1", "2") + self.assertRaises(TypeError, complex, "1", 42) + self.assertRaises(TypeError, complex, 1, "2") + self.assertRaises(ValueError, complex, "1+") + self.assertRaises(ValueError, complex, "1+1j+1j") + self.assertRaises(ValueError, complex, "--") + self.assertRaises(ValueError, complex, "(1+2j") + self.assertRaises(ValueError, complex, "1+2j)") + self.assertRaises(ValueError, complex, "1+(2j)") + self.assertRaises(ValueError, complex, "(1+2j)123") + if test_support.have_unicode: + self.assertRaises(ValueError, complex, unicode("x")) + #FIXME: these are raising wrong errors in Jython. + self.assertRaises(ValueError, complex, "1j+2") + self.assertRaises(ValueError, complex, "1e1ej") + self.assertRaises(ValueError, complex, "1e++1ej") + self.assertRaises(ValueError, complex, ")1+2j(") + # ] + + # the following three are accepted by Python 2.6 + #FIXME: these are raising wrong errors in Jython. + self.assertRaises(ValueError, complex, "1..1j") + self.assertRaises(ValueError, complex, "1.11.1j") + self.assertRaises(ValueError, complex, "1e1.1j") + # ] + + #FIXME: not working in Jython. + if test_support.have_unicode: + # check that complex accepts long unicode strings + self.assertEqual(type(complex(unicode("1"*500))), complex) + # ] + + class EvilExc(Exception): + pass + + class evilcomplex: + def __complex__(self): + raise EvilExc + + self.assertRaises(EvilExc, complex, evilcomplex()) + + class float2: + def __init__(self, value): + self.value = value + def __float__(self): + return self.value + + self.assertAlmostEqual(complex(float2(42.)), 42) + self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j) + self.assertRaises(TypeError, complex, float2(None)) + + class complex0(complex): + """Test usage of __complex__() when inheriting from 'complex'""" + def __complex__(self): + return 42j + + class complex1(complex): + """Test usage of __complex__() with a __new__() method""" + def __new__(self, value=0j): + return complex.__new__(self, 2*value) + def __complex__(self): + return self + + class complex2(complex): + """Make sure that __complex__() calls fail if anything other than a + complex is returned""" + def __complex__(self): + return None + + self.assertAlmostEqual(complex(complex0(1j)), 42j) + self.assertAlmostEqual(complex(complex1(1j)), 2j) + self.assertRaises(TypeError, complex, complex2(1j)) + + def test_constructor_jy(self): + # These are the parts of test_constructor that work in Jython. + # Delete this test when test_constructor skip is removed. + class OS: + def __init__(self, value): self.value = value + def __complex__(self): return self.value + class NS(object): + def __init__(self, value): self.value = value + def __complex__(self): return self.value + self.assertEqual(complex(OS(1+10j)), 1+10j) + self.assertEqual(complex(NS(1+10j)), 1+10j) + self.assertRaises(TypeError, complex, OS(None)) + self.assertRaises(TypeError, complex, NS(None)) + + self.assertAlmostEqual(complex("1+10j"), 1+10j) + self.assertAlmostEqual(complex(10), 10+0j) + self.assertAlmostEqual(complex(10.0), 10+0j) + self.assertAlmostEqual(complex(10L), 10+0j) + self.assertAlmostEqual(complex(10+0j), 10+0j) + self.assertAlmostEqual(complex(1,10), 1+10j) + self.assertAlmostEqual(complex(1,10L), 1+10j) + self.assertAlmostEqual(complex(1,10.0), 1+10j) + self.assertAlmostEqual(complex(1L,10), 1+10j) + self.assertAlmostEqual(complex(1L,10L), 1+10j) + self.assertAlmostEqual(complex(1L,10.0), 1+10j) + self.assertAlmostEqual(complex(1.0,10), 1+10j) + self.assertAlmostEqual(complex(1.0,10L), 1+10j) + self.assertAlmostEqual(complex(1.0,10.0), 1+10j) + self.assertAlmostEqual(complex(3.14+0j), 3.14+0j) + self.assertAlmostEqual(complex(3.14), 3.14+0j) + self.assertAlmostEqual(complex(314), 314.0+0j) + self.assertAlmostEqual(complex(314L), 314.0+0j) + self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j) + self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j) + self.assertAlmostEqual(complex(314, 0), 314.0+0j) + self.assertAlmostEqual(complex(314L, 0L), 314.0+0j) + self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j) + self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j) + self.assertAlmostEqual(complex(0j, 3.14), 3.14j) + self.assertAlmostEqual(complex(0.0, 3.14), 3.14j) + self.assertAlmostEqual(complex("1"), 1+0j) + self.assertAlmostEqual(complex("1j"), 1j) + self.assertAlmostEqual(complex(), 0) + self.assertAlmostEqual(complex("-1"), -1) + self.assertAlmostEqual(complex("+1"), +1) + #FIXME: these are not working in Jython. #self.assertAlmostEqual(complex("(1+2j)"), 1+2j) #self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j) self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j) @@ -458,7 +641,7 @@ for num in nums: self.assertAlmostEqual((num.real**2 + num.imag**2) ** 0.5, abs(num)) - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") + @unittest.skipIf(test_support.is_jython, "FIXME: str.__complex__ not working in Jython") def test_repr(self): self.assertEqual(repr(1+6j), '(1+6j)') self.assertEqual(repr(1-6j), '(1-6j)') @@ -482,6 +665,32 @@ self.assertEqual(repr(complex(0, -INF)), "-infj") self.assertEqual(repr(complex(0, NAN)), "nanj") + def test_repr_jy(self): + # These are just the cases that Jython can do from test_repr + # Delete this test when test_repr passes + self.assertEqual(repr(1+6j), '(1+6j)') + self.assertEqual(repr(1-6j), '(1-6j)') + + self.assertNotEqual(repr(-(1+0j)), '(-1+-0j)') + + # Fails to round-trip: +# self.assertEqual(1-6j,complex(repr(1-6j))) +# self.assertEqual(1+6j,complex(repr(1+6j))) +# self.assertEqual(-6j,complex(repr(-6j))) +# self.assertEqual(6j,complex(repr(6j))) + + self.assertEqual(repr(complex(1., INF)), "(1+infj)") + self.assertEqual(repr(complex(1., -INF)), "(1-infj)") + self.assertEqual(repr(complex(INF, 1)), "(inf+1j)") + self.assertEqual(repr(complex(-INF, INF)), "(-inf+infj)") + self.assertEqual(repr(complex(NAN, 1)), "(nan+1j)") + self.assertEqual(repr(complex(1, NAN)), "(1+nanj)") + self.assertEqual(repr(complex(NAN, NAN)), "(nan+nanj)") + + self.assertEqual(repr(complex(0, INF)), "infj") + self.assertEqual(repr(complex(0, -INF)), "-infj") + self.assertEqual(repr(complex(0, NAN)), "nanj") + def test_neg(self): self.assertEqual(-(1+6j), -1-6j) @@ -501,7 +710,6 @@ fo.close() test_support.unlink(test_support.TESTFN) - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") def test_getnewargs(self): self.assertEqual((1+2j).__getnewargs__(), (1.0, 2.0)) self.assertEqual((1-2j).__getnewargs__(), (1.0, -2.0)) @@ -557,7 +765,6 @@ self.assertFloatsAreIdentical(0.0 + z.imag, 0.0 + roundtrip.imag) - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") def test_format(self): # empty format string is same as str() self.assertEqual(format(1+3j, ''), str(1+3j)) diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -76,8 +76,6 @@ # Double check. self.assertEqual(type(int(n)), type(n)) - @unittest.skipIf(test_support.is_jython, - "FIXME: int boundries not right on Jython") def test_conversion_to_int(self): # Check that floats within the range of an int convert to type # int, not long. (issue #11144.) @@ -638,16 +636,12 @@ if not math.isnan(arg) and copysign(1.0, arg) > 0.0: self.assertEqual(fmt % -arg, '-' + rhs) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working on Jython") def test_issue5864(self): self.assertEqual(format(123.456, '.4'), '123.5') self.assertEqual(format(1234.56, '.4'), '1.235e+03') self.assertEqual(format(12345.6, '.4'), '1.235e+04') class ReprTestCase(unittest.TestCase): - @unittest.skipIf(test_support.is_jython, - "FIXME: not working on Jython") def test_repr(self): floats_file = open(os.path.join(os.path.split(__file__)[0], 'floating_points.txt')) @@ -736,11 +730,9 @@ self.assertRaises(TypeError, round, INF, 0.0) self.assertRaises(TypeError, round, -INF, 1.0) - self.assertRaises(TypeError, round, NAN, "ceci n'est pas un integer") + self.assertRaises(TypeError, round, NAN, "ceci n'est pas un entier") self.assertRaises(TypeError, round, -0.0, 1j) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working in Jython") def test_large_n(self): for n in [324, 325, 400, 2**31-1, 2**31, 2**32, 2**100]: self.assertEqual(round(123.456, n), 123.456) @@ -753,8 +745,6 @@ self.assertEqual(round(1e150, 309), 1e150) self.assertEqual(round(1.4e-315, 315), 1e-315) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working in Jython") def test_small_n(self): for n in [-308, -309, -400, 1-2**31, -2**31, -2**31-1, -2**100]: self.assertEqual(round(123.456, n), 0.0) @@ -762,8 +752,6 @@ self.assertEqual(round(1e300, n), 0.0) self.assertEqual(round(1e-320, n), 0.0) - @unittest.skipIf(test_support.is_jython, - "FIXME: not working in Jython") def test_overflow(self): self.assertRaises(OverflowError, round, 1.6e308, -308) self.assertRaises(OverflowError, round, -1.7e308, -308) @@ -855,7 +843,7 @@ @unittest.skipIf(test_support.is_jython, - "FIXME: formatting specials imperfect in Jython") + "FIXME: %-formatting specials imperfect in Jython") @requires_IEEE_754 def test_format_specials(self): # Test formatting of nans and infs. @@ -890,6 +878,42 @@ test(sfmt, NAN, ' nan') test(sfmt, -NAN, ' nan') + @requires_IEEE_754 + def test_format_specials_jy(self): + # Test formatting of nans and infs (suppressing %-formatting). + # This is just a crudely restricted copy of test_format_specials. + # Delete this test when we no longer have to skip test_format_specials. + + def test(fmt, value, expected): + # Test with only format(). + #self.assertEqual(fmt % value, expected, fmt) + if not '#' in fmt: + # Until issue 7094 is implemented, format() for floats doesn't + # support '#' formatting + fmt = fmt[1:] # strip off the % + self.assertEqual(format(value, fmt), expected, fmt) + + for fmt in ['%e', '%f', '%g', '%.0e', '%.6f', '%.20g', + '%#e', '%#f', '%#g', '%#.20e', '%#.15f', '%#.3g']: + pfmt = '%+' + fmt[1:] + sfmt = '% ' + fmt[1:] + test(fmt, INF, 'inf') + test(fmt, -INF, '-inf') + test(fmt, NAN, 'nan') + test(fmt, -NAN, 'nan') + # When asking for a sign, it's always provided. nans are + # always positive. + test(pfmt, INF, '+inf') + test(pfmt, -INF, '-inf') + test(pfmt, NAN, '+nan') + test(pfmt, -NAN, '+nan') + # When using ' ' for a sign code, only infs can be negative. + # Others have a space. + test(sfmt, INF, ' inf') + test(sfmt, -INF, '-inf') + test(sfmt, NAN, ' nan') + test(sfmt, -NAN, ' nan') + # Beginning with Python 2.6 float has cross platform compatible # ways to create and represent inf and nan diff --git a/Lib/test/test_float_jy.py b/Lib/test/test_float_jy.py --- a/Lib/test/test_float_jy.py +++ b/Lib/test/test_float_jy.py @@ -14,19 +14,22 @@ def test_float_repr(self): self.assertEqual(repr(12345678.000000005), '12345678.000000006') self.assertEqual(repr(12345678.0000000005), '12345678.0') - self.assertEqual(repr(math.pi**-100), - jython and '1.9275814160560203e-50' or '1.9275814160560206e-50') + self.assertEqual(repr(math.pi**-100), '1.9275814160560206e-50') self.assertEqual(repr(-1.0), '-1.0') - self.assertEqual(repr(-9876.543210), - jython and '-9876.54321' or '-9876.5432099999998') + self.assertEqual(repr(-9876.543210), '-9876.54321') self.assertEqual(repr(0.123456789e+35), '1.23456789e+34') + def test_float_repr2(self): + # Quite possibly these divergences result from JDK bug JDK-4511638: + self.assertEqual(repr(9876.543210e+15), + jython and '9.876543209999999e+18' or '9.87654321e+18') + self.assertEqual(repr(1235235235235240000.0), + jython and '1.2352352352352399e+18' or '1.23523523523524e+18') + def test_float_str(self): self.assertEqual(str(12345678.000005), '12345678.0') - self.assertEqual(str(12345678.00005), - jython and '12345678.0' or '12345678.0001') - self.assertEqual(str(12345678.00005), - jython and '12345678.0' or '12345678.0001') + self.assertEqual(str(12345678.00005), '12345678.0001') + self.assertEqual(str(12345678.00005), '12345678.0001') self.assertEqual(str(12345678.0005), '12345678.0005') self.assertEqual(str(math.pi**-100), '1.92758141606e-50') self.assertEqual(str(0.0), '0.0') diff --git a/src/org/python/core/PyComplex.java b/src/org/python/core/PyComplex.java --- a/src/org/python/core/PyComplex.java +++ b/src/org/python/core/PyComplex.java @@ -1,13 +1,10 @@ -/* - * Copyright (c) Corporation for National Research Initiatives - * Copyright (c) Jython Developers - */ +// Copyright (c) Corporation for National Research Initiatives +// Copyright (c) Jython Developers package org.python.core; -import org.python.core.stringlib.Formatter; -import org.python.core.stringlib.InternalFormatSpec; -import org.python.core.stringlib.InternalFormatSpecParser; - +import org.python.core.stringlib.FloatFormatter; +import org.python.core.stringlib.InternalFormat; +import org.python.core.stringlib.InternalFormat.Spec; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; @@ -22,6 +19,11 @@ public static final PyType TYPE = PyType.fromClass(PyComplex.class); + /** Format specification used by repr(). */ + static final Spec SPEC_REPR = InternalFormat.fromText(" >r"); // but also minFracDigits=0 + /** Format specification used by str() and none-format. (As CPython, but is that right?) */ + static final Spec SPEC_STR = InternalFormat.fromText(" >.12g"); + static PyComplex J = new PyComplex(0, 1.); @ExposedGet(doc = BuiltinDocs.complex_real_doc) @@ -46,7 +48,7 @@ @ExposedNew public static PyObject complex_new(PyNewWrapper new_, boolean init, PyType subtype, - PyObject[] args, String[] keywords) { + PyObject[] args, String[] keywords) { ArgParser ap = new ArgParser("complex", args, keywords, "real", "imag"); PyObject real = ap.getPyObject(0, Py.Zero); PyObject imag = ap.getPyObject(1, null); @@ -54,14 +56,12 @@ // Special-case for single argument that is already complex if (real.getType() == TYPE && new_.for_type == subtype && imag == null) { return real; - } - if (real instanceof PyString) { + } else if (real instanceof PyString) { if (imag != null) { throw Py.TypeError("complex() can't take second arg if first is a string"); } return real.__complex__(); - } - if (imag != null && imag instanceof PyString) { + } else if (imag != null && imag instanceof PyString) { throw Py.TypeError("complex() second arg can't be a string"); } @@ -79,7 +79,7 @@ PyComplex complexImag; PyFloat toFloat = null; if (real instanceof PyComplex) { - complexReal = (PyComplex) real; + complexReal = (PyComplex)real; } else { try { toFloat = real.__float__(); @@ -96,7 +96,7 @@ if (imag == null) { complexImag = new PyComplex(0.0); } else if (imag instanceof PyComplex) { - complexImag = (PyComplex) imag; + complexImag = (PyComplex)imag; } else { toFloat = null; try { @@ -129,7 +129,7 @@ public static String toString(double value) { if (value == Math.floor(value) && value <= Long.MAX_VALUE && value >= Long.MIN_VALUE) { - return Long.toString((long) value); + return Long.toString((long)value); } else { return Double.toString(value); } @@ -137,20 +137,51 @@ @Override public String toString() { - return complex_toString(); + return __str__().toString(); } - @ExposedMethod(names = {"__repr__", "__str__"}, doc = BuiltinDocs.complex___str___doc) - final String complex_toString() { - if (real == 0.) { - return toString(imag) + "j"; + @Override + public PyString __str__() { + return complex___str__(); + } + + @ExposedMethod(doc = BuiltinDocs.complex___str___doc) + final PyString complex___str__() { + return Py.newString(formatComplex(SPEC_STR)); + } + + @Override + public PyString __repr__() { + return complex___repr__(); + } + + @ExposedMethod(doc = BuiltinDocs.complex___repr___doc) + final PyString complex___repr__() { + return Py.newString(formatComplex(SPEC_REPR)); + } + + /** + * Format this complex according to the specification passed in. Supports __str__ + * and __repr__, and none-format. + *

+ * In general, the output is surrounded in parentheses, like "(12.34+24.68j)". + * However, if the real part is zero, only the imaginary part is printed, and without + * parentheses like "24.68j". The number format specification passed in is used + * without padding to width, for the real and imaginary parts individually. + * + * @param spec parsed format specification string + * @return formatted value + */ + private String formatComplex(Spec spec) { + FloatFormatter f = new FloatFormatter(spec, 2, 3); // Two elements + "(j)".length + // Even in r-format, complex strips *all* the trailing zeros. + f.setMinFracDigits(0); + if (real == 0.0) { + f.format(imag).append('j'); } else { - if (imag >= 0) { - return String.format("(%s+%sj)", toString(real), toString(imag)); - } else { - return String.format("(%s-%sj)", toString(real), toString(-imag)); - } + f.append('(').format(real).format(imag, "+").append("j)").pad(); } + return f.getResult(); } @Override @@ -164,7 +195,7 @@ return new PyFloat(real).hashCode(); } else { long v = Double.doubleToLongBits(real) ^ Double.doubleToLongBits(imag); - return (int) v ^ (int) (v >> 32); + return (int)v ^ (int)(v >> 32); } } @@ -282,21 +313,18 @@ } /** - * Coercion logic for complex. Implemented as a final method to avoid - * invocation of virtual methods from the exposed coerce. + * Coercion logic for complex. Implemented as a final method to avoid invocation of virtual + * methods from the exposed coerce. */ final PyObject complex___coerce_ex__(PyObject other) { if (other instanceof PyComplex) { return other; - } - if (other instanceof PyFloat) { - return new PyComplex(((PyFloat) other).getValue(), 0); - } - if (other instanceof PyInteger) { - return new PyComplex(((PyInteger) other).getValue(), 0); - } - if (other instanceof PyLong) { - return new PyComplex(((PyLong) other).doubleValue(), 0); + } else if (other instanceof PyFloat) { + return new PyComplex(((PyFloat)other).getValue(), 0); + } else if (other instanceof PyInteger) { + return new PyComplex(((PyInteger)other).getValue(), 0); + } else if (other instanceof PyLong) { + return new PyComplex(((PyLong)other).doubleValue(), 0); } return Py.None; } @@ -308,16 +336,13 @@ private final PyComplex coerce(PyObject other) { if (other instanceof PyComplex) { - return (PyComplex) other; - } - if (other instanceof PyFloat) { - return new PyComplex(((PyFloat) other).getValue(), 0); - } - if (other instanceof PyInteger) { - return new PyComplex(((PyInteger) other).getValue(), 0); - } - if (other instanceof PyLong) { - return new PyComplex(((PyLong) other).doubleValue(), 0); + return (PyComplex)other; + } else if (other instanceof PyFloat) { + return new PyComplex(((PyFloat)other).getValue(), 0); + } else if (other instanceof PyInteger) { + return new PyComplex(((PyInteger)other).getValue(), 0); + } else if (other instanceof PyLong) { + return new PyComplex(((PyLong)other).doubleValue(), 0); } throw Py.TypeError("xxx"); } @@ -377,8 +402,8 @@ } private final static PyObject _mul(PyComplex o1, PyComplex o2) { - return new PyComplex(o1.real * o2.real - o1.imag * o2.imag, - o1.real * o2.imag + o1.imag * o2.real); + return new PyComplex(o1.real * o2.real - o1.imag * o2.imag, // + o1.real * o2.imag + o1.imag * o2.real); } @Override @@ -417,14 +442,14 @@ } double ratio = b.imag / b.real; double denom = b.real + b.imag * ratio; - return new PyComplex((a.real + a.imag * ratio) / denom, - (a.imag - a.real * ratio) / denom); + return new PyComplex((a.real + a.imag * ratio) / denom, // + (a.imag - a.real * ratio) / denom); } else { /* divide tops and bottom by b.imag */ double ratio = b.real / b.imag; double denom = b.real * ratio + b.imag; - return new PyComplex((a.real * ratio + a.imag) / denom, - (a.imag * ratio - a.real) / denom); + return new PyComplex((a.real * ratio + a.imag) / denom, // + (a.imag * ratio - a.real) / denom); } } @@ -437,8 +462,7 @@ final PyObject complex___div__(PyObject right) { if (!canCoerce(right)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic complex division"); } return _div(this, coerce(right)); @@ -453,8 +477,7 @@ final PyObject complex___rdiv__(PyObject left) { if (!canCoerce(left)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic complex division"); } return _div(coerce(left), this); @@ -550,7 +573,7 @@ private static PyObject _mod(PyComplex value, PyComplex right) { Py.warning(Py.DeprecationWarning, "complex divmod(), // and % are deprecated"); - PyComplex z = (PyComplex) _div(value, right); + PyComplex z = (PyComplex)_div(value, right); z.real = Math.floor(z.real); z.imag = 0.0; @@ -586,7 +609,7 @@ private static PyObject _divmod(PyComplex value, PyComplex right) { Py.warning(Py.DeprecationWarning, "complex divmod(), // and % are deprecated"); - PyComplex z = (PyComplex) _div(value, right); + PyComplex z = (PyComplex)_div(value, right); z.real = Math.floor(z.real); z.imag = 0.0; @@ -636,12 +659,12 @@ return complex___pow__(right, modulo); } - @ExposedMethod(type = MethodType.BINARY, defaults = "null", doc = BuiltinDocs.complex___pow___doc) + @ExposedMethod(type = MethodType.BINARY, defaults = "null", + doc = BuiltinDocs.complex___pow___doc) final PyObject complex___pow__(PyObject right, PyObject modulo) { if (modulo != null) { throw Py.ValueError("complex modulo"); - } - if (!canCoerce(right)) { + } else if (!canCoerce(right)) { return null; } return _pow(this, coerce(right)); @@ -677,7 +700,7 @@ } // Check for integral powers - int iexp = (int) yr; + int iexp = (int)yr; if (yi == 0 && yr == iexp && iexp >= -128 && iexp <= 128) { return ipow(value, iexp); } @@ -764,6 +787,7 @@ return new PyComplex(real, imag); } + @Override public PyComplex conjugate() { return complex_conjugate(); } @@ -775,7 +799,7 @@ @ExposedMethod(doc = BuiltinDocs.complex___getnewargs___doc) final PyTuple complex___getnewargs__() { - return new PyTuple(new PyComplex(real, imag)); + return new PyTuple(new PyFloat(real), new PyFloat(imag)); } @Override @@ -790,43 +814,44 @@ @ExposedMethod(doc = BuiltinDocs.complex___format___doc) final PyObject complex___format__(PyObject formatSpec) { - return formatImpl(real, imag, formatSpec); - } - - static PyObject formatImpl(double r, double i, PyObject formatSpec) { if (!(formatSpec instanceof PyString)) { throw Py.TypeError("__format__ requires str or unicode"); } - PyString formatSpecStr = (PyString) formatSpec; + PyString formatSpecStr = (PyString)formatSpec; String result; try { String specString = formatSpecStr.getString(); - InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse(); - switch (spec.type) { - case '\0': /* No format code: like 'g', but with at least one decimal. */ - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case 'n': - case '%': - result = Formatter.formatComplex(r, i, spec); - break; - default: - /* unknown */ - throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'complex'", - spec.type)); + Spec spec = InternalFormat.fromText(specString); + if (spec.type!=Spec.NONE && "efgEFGn%".indexOf(spec.type) < 0) { + throw FloatFormatter.unknownFormat(spec.type, "complex"); + } else if (spec.alternate) { + throw FloatFormatter.alternateFormNotAllowed("complex"); + } else if (spec.fill == '0') { + throw FloatFormatter.zeroPaddingNotAllowed("complex"); + } else if (spec.align == '=') { + throw FloatFormatter.alignmentNotAllowed('=', "complex"); + } else { + if (spec.type == Spec.NONE) { + // In none-format, we take the default type and precision from __str__. + spec = spec.withDefaults(SPEC_STR); + // And then we use the __str__ mechanism to get parentheses or real 0 elision. + result = formatComplex(spec); + } else { + // In any other format, the defaults those commonly used for numeric formats. + spec = spec.withDefaults(Spec.NUMERIC); + FloatFormatter f = new FloatFormatter(spec, 2, 1);// 2 floats + "j" + // Convert as both parts per specification + f.format(real).format(imag, "+").append('j').pad(); + result = f.getResult(); + } } } catch (IllegalArgumentException e) { - throw Py.ValueError(e.getMessage()); + throw Py.ValueError(e.getMessage()); // XXX Can this be reached? } return formatSpecStr.createInstance(result); } - @Override public boolean isNumberType() { return true; diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java --- a/src/org/python/core/PyFloat.java +++ b/src/org/python/core/PyFloat.java @@ -1,23 +1,20 @@ -/* - * Copyright (c) Corporation for National Research Initiatives - * Copyright (c) Jython Developers - */ +// Copyright (c) Corporation for National Research Initiatives +// Copyright (c) Jython Developers package org.python.core; -import org.python.core.stringlib.Formatter; -import org.python.core.stringlib.InternalFormatSpec; -import org.python.core.stringlib.InternalFormatSpecParser; -import org.python.modules.math; import java.io.Serializable; import java.math.BigDecimal; -import java.math.RoundingMode; +import org.python.core.stringlib.FloatFormatter; +import org.python.core.stringlib.InternalFormat; +import org.python.core.stringlib.InternalFormat.Spec; import org.python.expose.ExposedClassMethod; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; import org.python.expose.ExposedType; import org.python.expose.MethodType; +import org.python.modules.math; /** * A builtin python float. @@ -27,9 +24,10 @@ public static final PyType TYPE = PyType.fromClass(PyFloat.class); - /** Precisions used by repr() and str(), respectively. */ - private static final int PREC_REPR = 17; - private static final int PREC_STR = 12; + /** Format specification used by repr(). */ + static final Spec SPEC_REPR = InternalFormat.fromText(" >r"); + /** Format specification used by str(). */ + static final Spec SPEC_STR = Spec.NUMERIC; private final double value; @@ -47,12 +45,12 @@ } public PyFloat(float v) { - this((double) v); + this((double)v); } @ExposedNew public static PyObject float_new(PyNewWrapper new_, boolean init, PyType subtype, - PyObject[] args, String[] keywords) { + PyObject[] args, String[] keywords) { ArgParser ap = new ArgParser("float", args, keywords, new String[] {"x"}, 0); PyObject x = ap.getPyObject(0, null); if (x == null) { @@ -69,9 +67,9 @@ if (e.match(Py.AttributeError)) { // Translate AttributeError to TypeError // XXX: We are using the same message as CPython, even if - // it is not strictly correct (instances of types - // that implement the __float__ method are also - // valid arguments) + // it is not strictly correct (instances of types + // that implement the __float__ method are also + // valid arguments) throw Py.TypeError("float() argument must be a string or a number"); } throw e; @@ -96,9 +94,9 @@ @ExposedClassMethod(doc = BuiltinDocs.float_fromhex_doc) public static PyObject float_fromhex(PyType type, PyObject o) { - //XXX: I'm sure this could be shortened/simplified, but Double.parseDouble() takes - // non-hex strings and requires the form 0xNUMBERpNUMBER for hex input which - // causes extra complexity here. + // XXX: I'm sure this could be shortened/simplified, but Double.parseDouble() takes + // non-hex strings and requires the form 0xNUMBERpNUMBER for hex input which + // causes extra complexity here. String message = "invalid hexadecimal floating-point string"; boolean negative = false; @@ -108,19 +106,16 @@ if (value.length() == 0) { throw Py.ValueError(message); - } - if (value.equals("nan") || value.equals("-nan") || value.equals("+nan")) { + } else if (value.equals("nan") || value.equals("-nan") || value.equals("+nan")) { return new PyFloat(Double.NaN); - } - if (value.equals("inf") ||value.equals("infinity") || - value.equals("+inf") ||value.equals("+infinity")) { + } else if (value.equals("inf") || value.equals("infinity") || value.equals("+inf") + || value.equals("+infinity")) { return new PyFloat(Double.POSITIVE_INFINITY); - } - if (value.equals("-inf") || value.equals("-infinity")) { + } else if (value.equals("-inf") || value.equals("-infinity")) { return new PyFloat(Double.NEGATIVE_INFINITY); } - //Strip and record + or - + // Strip and record + or - if (value.charAt(0) == '-') { value = value.substring(1); negative = true; @@ -131,17 +126,17 @@ throw Py.ValueError(message); } - //Append 0x if not present. + // Append 0x if not present. if (!value.startsWith("0x") && !value.startsWith("0X")) { value = "0x" + value; } - //reattach - if needed. + // reattach - if needed. if (negative) { value = "-" + value; } - //Append p if not present. + // Append p if not present. if (value.indexOf('p') == -1) { value = value + "p0"; } @@ -159,7 +154,7 @@ // @ExposedClassMethod(doc = BuiltinDocs.float_hex_doc) // public static PyObject float_hex(PyType type, double value) { - // return new PyString(Double.toHexString(value)); + // return new PyString(Double.toHexString(value)); // } private String pyHexString(Double f) { @@ -167,25 +162,31 @@ // the most efficient, but we don't expect this to be a hot // spot in our code either String java_hex = Double.toHexString(getValue()); - if (java_hex.equals("Infinity")) return "inf"; - if (java_hex.equals("-Infinity")) return "-inf"; - if (java_hex.equals("NaN")) return "nan"; - if (java_hex.equals("0x0.0p0")) return "0x0.0p+0"; - if (java_hex.equals("-0x0.0p0")) return "-0x0.0p+0"; + if (java_hex.equals("Infinity")) { + return "inf"; + } else if (java_hex.equals("-Infinity")) { + return "-inf"; + } else if (java_hex.equals("NaN")) { + return "nan"; + } else if (java_hex.equals("0x0.0p0")) { + return "0x0.0p+0"; + } else if (java_hex.equals("-0x0.0p0")) { + return "-0x0.0p+0"; + } - // replace hex rep of MpE to conform with Python such that - // 1. M is padded to 16 digits (ignoring a leading -) - // 2. Mp+E if E>=0 + // replace hex rep of MpE to conform with Python such that + // 1. M is padded to 16 digits (ignoring a leading -) + // 2. Mp+E if E>=0 // example: result of 42.0.hex() is translated from - // 0x1.5p5 to 0x1.5000000000000p+5 + // 0x1.5p5 to 0x1.5000000000000p+5 int len = java_hex.length(); boolean start_exponent = false; StringBuilder py_hex = new StringBuilder(len + 1); int padding = f > 0 ? 17 : 18; - for (int i=0; i < len; i++) { + for (int i = 0; i < len; i++) { char c = java_hex.charAt(i); if (c == 'p') { - for (int pad=i; pad < padding; pad++) { + for (int pad = i; pad < padding; pad++) { py_hex.append('0'); } start_exponent = true; @@ -194,9 +195,9 @@ py_hex.append('+'); } start_exponent = false; - } + } py_hex.append(c); - } + } return py_hex.toString(); } @@ -224,7 +225,7 @@ @ExposedMethod(doc = BuiltinDocs.float___str___doc) final PyString float___str__() { - return Py.newString(formatDouble(PREC_STR)); + return Py.newString(formatDouble(SPEC_STR)); } @Override @@ -234,34 +235,19 @@ @ExposedMethod(doc = BuiltinDocs.float___repr___doc) final PyString float___repr__() { - return Py.newString(formatDouble(PREC_REPR)); + return Py.newString(formatDouble(SPEC_REPR)); } - private String formatDouble(int precision) { - if (Double.isNaN(value)) { - return "nan"; - } else if (value == Double.NEGATIVE_INFINITY) { - return "-inf"; - } else if (value == Double.POSITIVE_INFINITY) { - return "inf"; - } - - String result = String.format("%%.%dg", precision); - result = Py.newString(result).__mod__(this).toString(); - - int i = 0; - if (result.startsWith("-")) { - i++; - } - for (; i < result.length(); i++) { - if (!Character.isDigit(result.charAt(i))) { - break; - } - } - if (i == result.length()) { - result += ".0"; - } - return result; + /** + * Format this float according to the specification passed in. Supports __str__ and + * __repr__. + * + * @param spec parsed format specification string + * @return formatted value + */ + private String formatDouble(Spec spec) { + FloatFormatter f = new FloatFormatter(spec); + return f.format(value).getResult(); } @Override @@ -274,23 +260,22 @@ double value = getValue(); if (Double.isInfinite(value)) { return value < 0 ? -271828 : 314159; - } - if (Double.isNaN(value)) { + } else if (Double.isNaN(value)) { return 0; } - + double intPart = Math.floor(value); double fractPart = value - intPart; if (fractPart == 0) { if (intPart <= Integer.MAX_VALUE && intPart >= Integer.MIN_VALUE) { - return (int) value; + return (int)value; } else { return __long__().hashCode(); } } else { long v = Double.doubleToLongBits(getValue()); - return (int) v ^ (int) (v >> 32); + return (int)v ^ (int)(v >> 32); } } @@ -307,10 +292,9 @@ @Override public Object __tojava__(Class c) { if (c == Double.TYPE || c == Number.class || c == Double.class || c == Object.class - || c == Serializable.class) { + || c == Serializable.class) { return new Double(getValue()); - } - if (c == Float.TYPE || c == Float.class) { + } else if (c == Float.TYPE || c == Float.class) { return new Float(getValue()); } return super.__tojava__(c); @@ -345,7 +329,7 @@ @Override public PyObject __ge__(PyObject other) { - //NaN >= anything is always false. + // NaN >= anything is always false. if (Double.isNaN(getValue())) { return Py.False; } @@ -354,7 +338,7 @@ @Override public PyObject __lt__(PyObject other) { - //NaN < anything is always false. + // NaN < anything is always false. if (Double.isNaN(getValue())) { return Py.False; } @@ -363,7 +347,7 @@ @Override public PyObject __le__(PyObject other) { - //NaN >= anything is always false. + // NaN >= anything is always false. if (Double.isNaN(getValue())) { return Py.False; } @@ -382,7 +366,7 @@ double j; if (other instanceof PyFloat) { - j = ((PyFloat) other).getValue(); + j = ((PyFloat)other).getValue(); } else if (!isFinite()) { // we're infinity: our magnitude exceeds any finite // integer, so it doesn't matter which int we compare i @@ -393,10 +377,10 @@ return -2; } } else if (other instanceof PyInteger) { - j = ((PyInteger) other).getValue(); + j = ((PyInteger)other).getValue(); } else if (other instanceof PyLong) { BigDecimal v = new BigDecimal(getValue()); - BigDecimal w = new BigDecimal(((PyLong) other).getValue()); + BigDecimal w = new BigDecimal(((PyLong)other).getValue()); return v.compareTo(w); } else { return -2; @@ -425,21 +409,18 @@ } /** - * Coercion logic for float. Implemented as a final method to avoid - * invocation of virtual methods from the exposed coerce. + * Coercion logic for float. Implemented as a final method to avoid invocation of virtual + * methods from the exposed coerce. */ final Object float___coerce_ex__(PyObject other) { if (other instanceof PyFloat) { return other; + } else if (other instanceof PyInteger) { + return new PyFloat((double)((PyInteger)other).getValue()); + } else if (other instanceof PyLong) { + return new PyFloat(((PyLong)other).doubleValue()); } else { - if (other instanceof PyInteger) { - return new PyFloat((double) ((PyInteger) other).getValue()); - } - if (other instanceof PyLong) { - return new PyFloat(((PyLong) other).doubleValue()); - } else { - return Py.None; - } + return Py.None; } } @@ -449,11 +430,11 @@ private static double coerce(PyObject other) { if (other instanceof PyFloat) { - return ((PyFloat) other).getValue(); + return ((PyFloat)other).getValue(); } else if (other instanceof PyInteger) { - return ((PyInteger) other).getValue(); + return ((PyInteger)other).getValue(); } else if (other instanceof PyLong) { - return ((PyLong) other).doubleValue(); + return ((PyLong)other).doubleValue(); } else { throw Py.TypeError("xxx"); } @@ -544,8 +525,7 @@ final PyObject float___div__(PyObject right) { if (!canCoerce(right)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic float division"); } @@ -565,8 +545,7 @@ final PyObject float___rdiv__(PyObject left) { if (!canCoerce(left)) { return null; - } - if (Options.division_warning >= 2) { + } else if (Options.division_warning >= 2) { Py.warning(Py.DeprecationWarning, "classic float division"); } @@ -667,7 +646,7 @@ return null; } double rightv = coerce(right); - return new PyFloat(modulo(getValue(),rightv)); + return new PyFloat(modulo(getValue(), rightv)); } @Override @@ -729,18 +708,16 @@ return float___pow__(right, modulo); } - @ExposedMethod(type = MethodType.BINARY, defaults = "null", - doc = BuiltinDocs.float___pow___doc) + @ExposedMethod(type = MethodType.BINARY, defaults = "null", // + doc = BuiltinDocs.float___pow___doc) final PyObject float___pow__(PyObject right, PyObject modulo) { if (!canCoerce(right)) { return null; - } - - if (modulo != null) { + } else if (modulo != null) { throw Py.TypeError("pow() 3rd argument not allowed unless all arguments are integers"); } - return _pow( getValue(), coerce(right), modulo); + return _pow(getValue(), coerce(right), modulo); } @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.float___rpow___doc) @@ -769,8 +746,7 @@ if (value == 0.0) { if (iw < 0.0) { throw Py.ZeroDivisionError("0.0 cannot be raised to a negative power"); - } - if (Double.isNaN(iw)) { + } else if (Double.isNaN(iw)) { return new PyFloat(Double.NaN); } return new PyFloat(0); @@ -835,12 +811,18 @@ return float___int__(); } + /** Smallest value that cannot be represented as an int */ + private static double INT_LONG_BOUNDARY = -(double)Integer.MIN_VALUE; // 2^31 + @ExposedMethod(doc = BuiltinDocs.float___int___doc) final PyObject float___int__() { - if (getValue() <= Integer.MAX_VALUE && getValue() >= Integer.MIN_VALUE) { - return new PyInteger((int) getValue()); + double v = getValue(); + if (v < INT_LONG_BOUNDARY && v > -(INT_LONG_BOUNDARY + 1.0)) { + // v will fit into an int (when rounded towards zero). + return new PyInteger((int)v); + } else { + return __long__(); } - return __long__(); } @Override @@ -902,7 +884,7 @@ @ExposedMethod(doc = BuiltinDocs.float_is_integer_doc) final boolean float_is_integer() { if (Double.isInfinite(value)) { - return false; + return false; } return Math.floor(value) == value; } @@ -929,41 +911,30 @@ @ExposedMethod(doc = BuiltinDocs.float___format___doc) final PyObject float___format__(PyObject formatSpec) { - return formatImpl(getValue(), formatSpec); - } - - static PyObject formatImpl(double d, PyObject formatSpec) { if (!(formatSpec instanceof PyString)) { throw Py.TypeError("__format__ requires str or unicode"); } - PyString formatSpecStr = (PyString) formatSpec; + PyString formatSpecStr = (PyString)formatSpec; String result; try { String specString = formatSpecStr.getString(); - InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse(); - if (spec.type == '\0'){ - return (Py.newFloat(d)).__str__(); - } - switch (spec.type) { - case '\0': /* No format code: like 'g', but with at least one decimal. */ - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case 'n': - case '%': - result = Formatter.formatFloat(d, spec); - break; - default: - /* unknown */ - throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'", - spec.type)); + Spec spec = InternalFormat.fromText(specString); + if (spec.type!=Spec.NONE && "efgEFGn%".indexOf(spec.type) < 0) { + throw FloatFormatter.unknownFormat(spec.type, "float"); + } else if (spec.alternate) { + throw FloatFormatter.alternateFormNotAllowed("float"); + } else { + // spec may be incomplete. The defaults are those commonly used for numeric formats. + spec = spec.withDefaults(Spec.NUMERIC); + // Get a formatter for the spec. + FloatFormatter f = new FloatFormatter(spec); + // Convert as per specification. + f.format(value).pad(); + result = f.getResult(); } } catch (IllegalArgumentException e) { - throw Py.ValueError(e.getMessage()); + throw Py.ValueError(e.getMessage()); // XXX Can this be reached? } return formatSpecStr.createInstance(result); } @@ -979,14 +950,15 @@ PyTuple frexp = math.frexp(value); double float_part = ((Double)frexp.get(0)).doubleValue(); int exponent = ((Integer)frexp.get(1)).intValue(); - for (int i=0; i<300 && float_part != Math.floor(float_part); i++) { + for (int i = 0; i < 300 && float_part != Math.floor(float_part); i++) { float_part *= 2.0; exponent--; } - /* self == float_part * 2**exponent exactly and float_part is integral. - If FLT_RADIX != 2, the 300 steps may leave a tiny fractional part - to be truncated by PyLong_FromDouble(). */ - + /* + * self == float_part * 2**exponent exactly and float_part is integral. If FLT_RADIX != 2, + * the 300 steps may leave a tiny fractional part to be truncated by PyLong_FromDouble(). + */ + PyLong numerator = new PyLong(float_part); PyLong denominator = new PyLong(1); PyLong py_exponent = new PyLong(Math.abs(exponent)); @@ -1013,9 +985,7 @@ // but this is what Python demands public enum Format { - UNKNOWN("unknown"), - BE("IEEE, big-endian"), - LE("IEEE, little-endian"); + UNKNOWN("unknown"), BE("IEEE, big-endian"), LE("IEEE, little-endian"); private final String format; @@ -1027,6 +997,7 @@ return format; } } + // subset of IEEE-754, the JVM is big-endian public static volatile Format double_format = Format.BE; public static volatile Format float_format = Format.BE; @@ -1050,14 +1021,14 @@ } if (Format.LE.format().equals(format)) { throw Py.ValueError(String.format("can only set %s format to 'unknown' or the " - + "detected platform value", typestr)); + + "detected platform value", typestr)); } else if (Format.BE.format().equals(format)) { new_format = Format.BE; } else if (Format.UNKNOWN.format().equals(format)) { new_format = Format.UNKNOWN; } else { - throw Py.ValueError("__setformat__() argument 2 must be 'unknown', " + - "'IEEE, little-endian' or 'IEEE, big-endian'"); + throw Py.ValueError("__setformat__() argument 2 must be 'unknown', " + + "'IEEE, little-endian' or 'IEEE, big-endian'"); } if (new_format != null) { if ("double".equals(typestr)) { diff --git a/src/org/python/core/PyInteger.java b/src/org/python/core/PyInteger.java --- a/src/org/python/core/PyInteger.java +++ b/src/org/python/core/PyInteger.java @@ -1,7 +1,5 @@ -/* - * Copyright (c) Corporation for National Research Initiatives - * Copyright (c) Jython Developers - */ +// Copyright (c) Corporation for National Research Initiatives +// Copyright (c) Jython Developers package org.python.core; import java.io.Serializable; @@ -112,9 +110,9 @@ * Convert all sorts of object types to either PyInteger or PyLong, * using their {@link PyObject#__int__()} method, whether exposed or not, or if that raises an * exception (as the base PyObject one does), using any __trunc__() - * the type may have exposed. If all this fails, this method raises an exception. Equivalent to CPython - * PyNumber_Int(). - * + * the type may have exposed. If all this fails, this method raises an exception. Equivalent to + * CPython PyNumber_Int(). + * * @param x to convert to an int * @return int or long result. * @throws PyException (TypeError) if no method of conversion can be found @@ -151,7 +149,7 @@ /** * Helper called on whatever exposed method __trunc__ returned: it may be * int, long or something with an exposed __int__. - * + * * @return convert to an int. * @throws TypeError and AttributeError. */ @@ -160,7 +158,7 @@ PyObject i = integral.invoke("__int__"); if (!(i instanceof PyInteger) && !(i instanceof PyLong)) { throw Py.TypeError(String.format("__trunc__ returned non-Integral (type %.200s)", - integral.getType().fastGetName())); + integral.getType().fastGetName())); } return i; } @@ -225,7 +223,7 @@ @Override public Object __tojava__(Class c) { if (c == Integer.TYPE || c == Number.class || c == Object.class || c == Integer.class - || c == Serializable.class) { + || c == Serializable.class) { return new Integer(getValue()); } @@ -233,10 +231,10 @@ return new Boolean(getValue() != 0); } if (c == Byte.TYPE || c == Byte.class) { - return new Byte((byte) getValue()); + return new Byte((byte)getValue()); } if (c == Short.TYPE || c == Short.class) { - return new Short((short) getValue()); + return new Short((short)getValue()); } if (c == Long.TYPE || c == Long.class) { @@ -276,8 +274,8 @@ } /** - * Coercion logic for int. Implemented as a final method to avoid - * invocation of virtual methods from the exposed coerced. + * Coercion logic for int. Implemented as a final method to avoid invocation of virtual methods + * from the exposed coerced. */ final Object int___coerce_ex__(PyObject other) { return other instanceof PyInteger ? other : Py.None; @@ -289,7 +287,7 @@ private static final int coerce(PyObject other) { if (other instanceof PyInteger) { - return ((PyInteger) other).getValue(); + return ((PyInteger)other).getValue(); } throw Py.TypeError("xxx"); } @@ -311,7 +309,7 @@ if ((x ^ a) >= 0 || (x ^ b) >= 0) { return Py.newInteger(x); } - return new PyLong((long) a + (long) b); + return new PyLong((long)a + (long)b); } @Override @@ -329,7 +327,7 @@ if ((x ^ a) >= 0 || (x ^ ~b) >= 0) { return Py.newInteger(x); } - return new PyLong((long) a - (long) b); + return new PyLong((long)a - (long)b); } @Override @@ -374,7 +372,7 @@ x *= rightv; if (x <= Integer.MAX_VALUE && x >= Integer.MIN_VALUE) { - return Py.newInteger((int) x); + return Py.newInteger((int)x); } return __long__().__mul__(right); } @@ -400,12 +398,12 @@ // If the signs of x and y differ, and the remainder is non-0, C89 doesn't define // whether xdivy is now the floor or the ceiling of the infinitely precise - // quotient. We want the floor, and we have it iff the remainder's sign matches + // quotient. We want the floor, and we have it iff the remainder's sign matches // y's. if (xmody != 0 && ((y < 0 && xmody > 0) || (y > 0 && xmody < 0))) { xmody += y; --xdivy; - //assert(xmody && ((y ^ xmody) >= 0)); + // assert(xmody && ((y ^ xmody) >= 0)); } return xdivy; } @@ -568,8 +566,8 @@ return int___pow__(right, modulo); } - @ExposedMethod(type = MethodType.BINARY, defaults = {"null"}, - doc = BuiltinDocs.int___pow___doc) + @ExposedMethod(type = MethodType.BINARY, defaults = {"null"}, // + doc = BuiltinDocs.int___pow___doc) final PyObject int___pow__(PyObject right, PyObject modulo) { if (!canCoerce(right)) { return null; @@ -599,8 +597,8 @@ return __rpow__(left, null); } - private static PyObject _pow(int value, int pow, PyObject modulo, PyObject left, - PyObject right) { + private static PyObject _pow(int value, int pow, PyObject modulo,// + PyObject left, PyObject right) { int mod = 0; long tmp = value; boolean neg = false; @@ -663,7 +661,6 @@ return Py.newInteger(result); } - @Override public PyObject __lshift__(PyObject right) { return int___lshift__(right); @@ -673,7 +670,7 @@ final PyObject int___lshift__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__lshift__(right); } else { @@ -696,7 +693,7 @@ final PyObject int___rlshift__(PyObject left) { int leftv; if (left instanceof PyInteger) { - leftv = ((PyInteger) left).getValue(); + leftv = ((PyInteger)left).getValue(); } else if (left instanceof PyLong) { return left.__rlshift__(int___long__()); } else { @@ -724,7 +721,7 @@ final PyObject int___rshift__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__rshift__(right); } else { @@ -746,7 +743,7 @@ final PyObject int___rrshift__(PyObject left) { int leftv; if (left instanceof PyInteger) { - leftv = ((PyInteger) left).getValue(); + leftv = ((PyInteger)left).getValue(); } else if (left instanceof PyLong) { return left.__rshift__(int___long__()); } else { @@ -773,7 +770,7 @@ final PyObject int___and__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__and__(right); } else { @@ -797,7 +794,7 @@ final PyObject int___xor__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__xor__(right); } else { @@ -811,7 +808,7 @@ final PyObject int___rxor__(PyObject left) { int leftv; if (left instanceof PyInteger) { - leftv = ((PyInteger) left).getValue(); + leftv = ((PyInteger)left).getValue(); } else if (left instanceof PyLong) { return left.__rxor__(int___long__()); } else { @@ -830,7 +827,7 @@ final PyObject int___or__(PyObject right) { int rightv; if (right instanceof PyInteger) { - rightv = ((PyInteger) right).getValue(); + rightv = ((PyInteger)right).getValue(); } else if (right instanceof PyLong) { return int___long__().__or__(right); } else { @@ -921,7 +918,7 @@ @ExposedMethod(doc = BuiltinDocs.int___float___doc) final PyFloat int___float__() { - return new PyFloat((double) getValue()); + return new PyFloat((double)getValue()); } @Override @@ -979,7 +976,7 @@ @ExposedMethod(doc = BuiltinDocs.int___getnewargs___doc) final PyTuple int___getnewargs__() { - return new PyTuple(new PyObject[]{new PyInteger(this.getValue())}); + return new PyTuple(new PyObject[] {new PyInteger(this.getValue())}); } @Override @@ -1026,7 +1023,7 @@ throw Py.TypeError("__format__ requires str or unicode"); } - PyString formatSpecStr = (PyString) formatSpec; + PyString formatSpecStr = (PyString)formatSpec; String result; try { String specString = formatSpecStr.getString(); @@ -1052,10 +1049,10 @@ int sign; if (value instanceof Integer) { - int intValue = (Integer) value; + int intValue = (Integer)value; sign = intValue < 0 ? -1 : intValue == 0 ? 0 : 1; } else { - sign = ((BigInteger) value).signum(); + sign = ((BigInteger)value).signum(); } String strValue; @@ -1065,20 +1062,20 @@ if (spec.type == 'c') { if (spec.sign != '\0') { throw new IllegalArgumentException("Sign not allowed with integer format " - + "specifier 'c'"); + + "specifier 'c'"); } if (value instanceof Integer) { - int intValue = (Integer) value; + int intValue = (Integer)value; if (intValue > 0xffff) { throw new IllegalArgumentException("%c arg not in range(0x10000)"); } - strValue = Character.toString((char) intValue); + strValue = Character.toString((char)intValue); } else { - BigInteger bigInt = (BigInteger) value; + BigInteger bigInt = (BigInteger)value; if (bigInt.intValue() > 0xffff || bigInt.bitCount() > 16) { throw new IllegalArgumentException("%c arg not in range(0x10000)"); } - strValue = Character.toString((char) bigInt.intValue()); + strValue = Character.toString((char)bigInt.intValue()); } } else { int radix = 10; @@ -1099,21 +1096,21 @@ } else if (value instanceof BigInteger) { switch (radix) { case 2: - strValue = toBinString((BigInteger) value); + strValue = toBinString((BigInteger)value); break; case 8: - strValue = toOctString((BigInteger) value); + strValue = toOctString((BigInteger)value); break; case 16: - strValue = toHexString((BigInteger) value); + strValue = toHexString((BigInteger)value); break; default: // General case (v.slow in known implementations up to Java 7). - strValue = ((BigInteger) value).toString(radix); + strValue = ((BigInteger)value).toString(radix); break; } } else { - strValue = Integer.toString((Integer) value, radix); + strValue = Integer.toString((Integer)value, radix); } if (spec.alternate) { diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java --- a/src/org/python/core/PyString.java +++ b/src/org/python/core/PyString.java @@ -3766,6 +3766,16 @@ } } + /** + * Implements PEP-3101 {}-formatting methods str.format() and + * unicode.format(). + * + * @param value the format string + * @param args to be interpolated into the string + * @param keywords for the trailing args + * @param enclosingIterator when used nested + * @return the formatted string based on the arguments + */ protected String buildFormattedString(String value, PyObject[] args, String[] keywords, MarkupIterator enclosingIterator) { StringBuilder result = new StringBuilder(); @@ -3775,12 +3785,20 @@ if (chunk == null) { break; } + // A Chunk encapsulates a literal part ... result.append(chunk.literalText); + // ... and the parsed form of the replacement field that followed it (if any) if (chunk.fieldName.length() > 0) { + // The grammar of the replacement field is: + // "{" [field_name] ["!" conversion] [":" format_spec] "}" + + // Get the object referred to by the field name (which may be omitted). PyObject fieldObj = getFieldObject(chunk.fieldName, args, keywords); if (fieldObj == null) { continue; } + + // The conversion specifier is s = __str__ or r = __repr__. if ("r".equals(chunk.conversion)) { fieldObj = fieldObj.__repr__(); } else if ("s".equals(chunk.conversion)) { @@ -3788,12 +3806,15 @@ } else if (chunk.conversion != null) { throw Py.ValueError("Unknown conversion specifier " + chunk.conversion); } + + // The format_spec may be simple, or contained nested replacement fields. String formatSpec = chunk.formatSpec; if (chunk.formatSpecNeedsExpanding) { if (enclosingIterator != null) { // PEP 3101 says only 2 levels throw Py.ValueError("Max string recursion exceeded"); } + // Recursively interpolate further args into chunk.formatSpec formatSpec = buildFormattedString(formatSpec, args, keywords, it); } renderField(fieldObj, formatSpec, result); @@ -3802,6 +3823,15 @@ return result.toString(); } + /** + * Return the object referenced by a given field name, interpreted in the context of the given + * argument list, containing positional and keyword arguments. + * + * @param fieldName to interpret. + * @param args argument list (positional then keyword arguments). + * @param keywords naming the keyword arguments. + * @return the object designated or null. + */ private PyObject getFieldObject(String fieldName, PyObject[] args, String[] keywords) { FieldNameIterator iterator = new FieldNameIterator(fieldName); Object head = iterator.head(); @@ -3814,6 +3844,7 @@ throw Py.IndexError("tuple index out of range"); } obj = args[index]; + } else { for (int i = 0; i < keywords.length; i++) { if (keywords[i].equals(head)) { @@ -3825,6 +3856,7 @@ throw Py.KeyError((String)head); } } + if (obj != null) { while (true) { FieldNameIterator.Chunk chunk = iterator.nextChunk(); @@ -3844,9 +3876,18 @@ } } } + return obj; } + /** + * Append to a formatting result, the presentation of one object, according to a given format + * specification and the object's __format__ method. + * + * @param fieldObj to format. + * @param formatSpec specification to apply. + * @param result to which the result will be appended. + */ private void renderField(PyObject fieldObj, String formatSpec, StringBuilder result) { PyString formatSpecStr = formatSpec == null ? Py.EmptyString : new PyString(formatSpec); result.append(fieldObj.__format__(formatSpecStr).asString()); @@ -3876,10 +3917,12 @@ } /** - * Internal implementation of str.__format__() + * Format the given text according to a parsed PEP 3101 formatting specification, as during + * text.__format__(format_spec) or "{:s}".format(text) where + * text is a Python string. * - * @param text the text to format - * @param spec the PEP 3101 formatting specification + * @param text to format + * @param spec the parsed PEP 3101 formatting specification * @return the result of the formatting */ public static String formatString(String text, InternalFormatSpec spec) { @@ -3954,6 +3997,9 @@ } +/** + * Interpreter for %-format strings. (Note visible across the core package.) + */ final class StringFormatter { int index; @@ -3985,6 +4031,12 @@ this(format, false); } + /** + * Initialise the interpreter with the given format string, ready for {@link #format(PyObject)}. + * + * @param format string to interpret + * @param unicodeCoercion to indicate a PyUnicode result is expected + */ public StringFormatter(String format, boolean unicodeCoercion) { index = 0; this.format = format; @@ -3995,16 +4047,11 @@ PyObject getarg() { PyObject ret = null; switch (argIndex) { - // special index indicating a mapping - case -3: + case -3: // special index indicating a mapping return args; - // special index indicating a single item that has already been - // used - case -2: + case -2: // special index indicating a single item that has already been used break; - // special index indicating a single item that has not yet been - // used - case -1: + case -1: // special index indicating a single item that has not yet been used argIndex = -2; return args; default: diff --git a/src/org/python/core/__builtin__.java b/src/org/python/core/__builtin__.java --- a/src/org/python/core/__builtin__.java +++ b/src/org/python/core/__builtin__.java @@ -15,6 +15,7 @@ import java.util.Map; import org.python.antlr.base.mod; +import org.python.core.util.ExtraMath; import org.python.core.util.RelativeFile; import org.python.core.util.StringUtil; import org.python.modules._functools._functools; @@ -1601,20 +1602,14 @@ ArgParser ap = new ArgParser("round", args, kwds, new String[] {"number", "ndigits"}, 0); PyObject number = ap.getPyObject(0); int ndigits = ap.getIndex(1, 0); - return round(number.asDouble(), ndigits); - } - - private static PyFloat round(double f, int digits) { - boolean neg = f < 0; - double multiple = Math.pow(10., digits); - if (neg) { - f = -f; + double x = number.asDouble(); + double r = ExtraMath.round(x, ndigits); + if (Double.isInfinite(r) && !Double.isInfinite(x)) { + // Rounding caused magnitude to increase beyond representable range + throw Py.OverflowError("rounded value too large to represent"); + } else { + return new PyFloat(r); } - double tmp = Math.floor(f * multiple + 0.5); - if (neg) { - tmp = -tmp; - } - return new PyFloat(tmp / multiple); } } diff --git a/src/org/python/core/stringlib/FloatFormatter.java b/src/org/python/core/stringlib/FloatFormatter.java new file mode 100644 --- /dev/null +++ b/src/org/python/core/stringlib/FloatFormatter.java @@ -0,0 +1,880 @@ +// Copyright (c) Jython Developers +package org.python.core.stringlib; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.python.core.stringlib.InternalFormat.Spec; + +/** + * A class that provides the implementation of floating-point formatting. In a limited way, it acts + * like a StringBuilder to which text and one or more numbers may be appended, formatted according + * to the format specifier supplied at construction. These are ephemeral objects that are not, on + * their own, thread safe. + */ +public class FloatFormatter extends InternalFormat.Formatter { + + /** The rounding mode dominant in the formatter. */ + static final RoundingMode ROUND_PY = RoundingMode.HALF_UP; // Believed to be HALF_EVEN in Py3k + + /** If it contains no decimal point, this length is zero, and 1 otherwise. */ + private int lenPoint; + /** The length of the fractional part, right of the decimal point. */ + private int lenFraction; + /** The length of the exponent marker ("e"), "inf" or "nan", or zero if there isn't one. */ + private int lenMarker; + /** The length of the exponent sign and digits or zero if there isn't one. */ + private int lenExponent; + /** if >=0, minimum digits to follow decimal point (where consulted) */ + private int minFracDigits; + + /** + * Construct the formatter from a specification. A reference is held to this specification, but + * it will not be modified by the actions of this class. + * + * @param spec parsed conversion specification + */ + public FloatFormatter(Spec spec) { + // Space for result is based on padded width, or precision, whole part & furniture. + this(spec, 1, 0); + } + + /** + * Construct the formatter from a specification and an explicit initial buffer capacity. A + * reference is held to this specification, but it will not be modified by the actions of this + * class. + * + * @param spec parsed conversion specification + * @param width expected for the formatted result + */ + public FloatFormatter(Spec spec, int width) { + super(spec, width); + if (spec.alternate) { + // Alternate form means do not trim the zero fractional digits. + minFracDigits = -1; + } else if (spec.type == 'r' || spec.type == Spec.NONE) { + // These formats by default show at least one fractional digit. + minFracDigits = 1; + } else { + /* + * Every other format (if it does not ignore the setting) will by default trim off all + * the trailing zero fractional digits. + */ + minFracDigits = 0; + } + } + + /** + * Construct the formatter from a specification and two extra hints about the initial buffer + * capacity. A reference is held to this specification, but it will not be modified by the + * actions of this class. + * + * @param spec parsed conversion specification + * @param count of elements likely to be formatted + * @param margin for elements formatted only once + */ + public FloatFormatter(Spec spec, int count, int margin) { + /* + * Rule of thumb used here: in e format w = (p-1) + len("+1.e+300") = p+7; in f format w = p + * + len("1,000,000.") = p+10. If we're wrong, the result will have to grow. No big deal. + */ + this(spec, Math.max(spec.width + 1, count * (spec.precision + 10) + margin)); + } + + /** + * Override the default truncation behaviour for the specification originally supplied. Some + * formats remove trailing zero digits, trimming to zero or one. Set member + * minFracDigits, to modify this behaviour. + * + * @param minFracDigits if <0 prevent truncation; if >=0 the minimum number of fractional + * digits; when this is zero, and all fractional digits are zero, the decimal point + * will also be removed. + */ + public void setMinFracDigits(int minFracDigits) { + this.minFracDigits = minFracDigits; + } + + @Override + protected void reset() { + // Clear the variables describing the latest number in result. + super.reset(); + lenPoint = lenFraction = lenMarker = lenExponent = 0; + } + + @Override + protected int[] sectionLengths() { + return new int[] {lenSign, lenWhole, lenPoint, lenFraction, lenMarker, lenExponent}; + } + + /* + * Re-implement the text appends so they return the right type. + */ + @Override + public FloatFormatter append(char c) { + super.append(c); + return this; + } + + @Override + public FloatFormatter append(CharSequence csq) { + super.append(csq); + return this; + } + + @Override + public FloatFormatter append(CharSequence csq, int start, int end) // + throws IndexOutOfBoundsException { + super.append(csq, start, end); + return this; + } + + /** + * Format a floating-point number according to the specification represented by this + * FloatFormatter. + * + * @param value to convert + * @return this object + */ + public FloatFormatter format(double value) { + return format(value, null); + } + + /** + * Format a floating-point number according to the specification represented by this + * FloatFormatter. The conversion type, precision, and flags for grouping or + * percentage are dealt with here. At the point this is used, we know the {@link #spec} is one + * of the floating-point types. This entry point allows explicit control of the prefix of + * positive numbers, overriding defaults for the format type. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @return this object + */ + @SuppressWarnings("fallthrough") + public FloatFormatter format(double value, String positivePrefix) { + + // Puts all instance variables back to their starting defaults, and start = result.length(). + setStart(); + + // Precision defaults to 6 (or 12 for none-format) + int precision = spec.getPrecision(Spec.specified(spec.type) ? 6 : 12); + + /* + * By default, the prefix of a positive number is "", but the format specifier may override + * it, and the built-in type complex needs to override the format. + */ + if (positivePrefix == null && Spec.specified(spec.sign) && spec.sign != '-') { + positivePrefix = Character.toString(spec.sign); + } + + // Different process for each format type, ignoring case for now. + switch (Character.toLowerCase(spec.type)) { + case 'e': + // Exponential case: 1.23e-45 + format_e(value, positivePrefix, precision); + break; + + case 'f': + // Fixed case: 123.45 + format_f(value, positivePrefix, precision); + break; + + case 'n': + // Locale-sensitive version of g-format should be here. (D?sol? de vous decevoir.) + // XXX Set a variable here to signal localisation in/after groupDigits? + case 'g': + // General format: fixed or exponential according to value. + format_g(value, positivePrefix, precision, 0); + break; + + case Spec.NONE: + // None format like g-format but goes exponential at precision-1 + format_g(value, positivePrefix, precision, -1); + break; + + case 'r': + // For float.__repr__, very special case, breaks all the rules. + format_r(value, positivePrefix); + break; + + case '%': + // Multiplies by 100 and displays in f-format, followed by a percent sign. + format_f(100. * value, positivePrefix, precision); + result.append('%'); + break; + + default: + // Should never get here, since this was checked in PyFloat. + throw unknownFormat(spec.type, "float"); + } + + // If the format type is an upper-case letter, convert the result to upper case. + if (Character.isUpperCase(spec.type)) { + uppercase(); + } + + // If required to, group the whole-part digits. + if (spec.grouping) { + groupDigits(3, ','); + } + + return this; + } + + /** + * Convert just the letters in the representation of the current number (in {@link #result}) to + * upper case. (That's the exponent marker or the "inf" or "nan".) + */ + @Override + protected void uppercase() { + int letters = indexOfMarker(); + int end = letters + lenMarker; + for (int i = letters; i < end; i++) { + char c = result.charAt(i); + result.setCharAt(i, Character.toUpperCase(c)); + } + } + + /** + * Common code to deal with the sign, and the special cases "0", "-0", "nan, "inf", or "-inf". + * If the method returns false, we have started a non-zero number and the sign is + * already in {@link #result}. The client need then only encode abs(value). If the method + * returns true, and {@link #lenMarker}==0, the value was "0" or "-0": the caller + * may have to zero-extend this, and/or add an exponent, to match the requested format. If the + * method returns true, and {@link #lenMarker}>0, the method has placed "nan, "inf" + * in the {@link #result} buffer (preceded by a sign if necessary). + * + * @param value to convert + * @return true if the value was one of "0", "-0", "nan, "inf", or "-inf". + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + */ + private boolean signAndSpecialNumber(double value, String positivePrefix) { + + // This is easiest via the raw bits + long bits = Double.doubleToRawLongBits(value); + + // NaN is always positive + if (Double.isNaN(value)) { + bits &= ~SIGN_MASK; + } + + if ((bits & SIGN_MASK) != 0) { + // Negative: encode a minus sign and strip it off bits + result.append('-'); + lenSign = 1; + bits &= ~SIGN_MASK; + + } else if (positivePrefix != null) { + // Positive, and a prefix is required. Note CPython 2.7 produces "+nan", " nan". + result.append(positivePrefix); + lenSign = positivePrefix.length(); + } + + if (bits == 0L) { + // All zero means it's zero. (It may have been negative, producing -0.) + result.append('0'); + lenWhole = 1; + return true; + + } else if ((bits & EXP_MASK) == EXP_MASK) { + // This is characteristic of NaN or Infinity. + result.append(((bits & ~EXP_MASK) == 0L) ? "inf" : "nan"); + lenMarker = 3; + return true; + + } else { + return false; + } + } + + private static final long SIGN_MASK = 0x8000000000000000L; + private static final long EXP_MASK = 0x7ff0000000000000L; + + /** + * The e-format helper function of {@link #format(double, String)} that uses Java's + * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to + * the {@link #result} buffer, and {@link #start} will be set to the index of its first + * character. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @param precision precision (maximum number of fractional digits) + */ + private void format_e(double value, String positivePrefix, int precision) { + + // Exponent (default value is for 0.0 and -0.0) + int exp = 0; + + if (!signAndSpecialNumber(value, positivePrefix)) { + // Convert abs(value) to decimal with p+1 digits of accuracy. + MathContext mc = new MathContext(precision + 1, ROUND_PY); + BigDecimal vv = new BigDecimal(Math.abs(value), mc); + + // Take explicit control in order to get exponential notation out of BigDecimal. + String digits = vv.unscaledValue().toString(); + int digitCount = digits.length(); + result.append(digits.charAt(0)); + lenWhole = 1; + if (digitCount > 1) { + // There is a fractional part + result.append('.').append(digits.substring(1)); + lenPoint = 1; + lenFraction = digitCount - 1; + } + exp = lenFraction - vv.scale(); + } + + // Finally add zeros, as necessary, and stick on the exponent. + + if (lenMarker == 0) { + appendTrailingZeros(precision); + appendExponent(exp); + } + } + + /** + * The f-format inner helper function of {@link #format(double, String)} that uses Java's + * {@link BigDecimal} to provide conversion and rounding. The converted number is appended to + * the {@link #result} buffer, and {@link #start} will be set to the index of its first + * character. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @param precision precision (maximum number of fractional digits) + */ + private void format_f(double value, String positivePrefix, int precision) { + + if (signAndSpecialNumber(value, positivePrefix)) { + + if (lenMarker == 0) { + // May be 0 or -0 so we still need to ... + appendTrailingZeros(precision); + } + + } else { + // Convert value to decimal exactly. (This can be very long.) + BigDecimal vLong = new BigDecimal(Math.abs(value)); + + // Truncate to the defined number of places to the right of the decimal point). + BigDecimal vv = vLong.setScale(precision, ROUND_PY); + + // When converted to text, the number of fractional digits is exactly the scale we set. + String raw = vv.toPlainString(); + result.append(raw); + if ((lenFraction = vv.scale()) > 0) { + // There is a decimal point and some digits following + lenWhole = result.length() - (start + lenSign + (lenPoint = 1) + lenFraction); + } else { + lenWhole = result.length() - (start + lenSign); + } + + } + } + + /** + * Implementation of the variants of g-format, that uses Java's {@link BigDecimal} to provide + * conversion and rounding. These variants are g-format proper, alternate g-format (available + * for "%#g" formatting), n-format (as g but subsequently "internationalised"), and none-format + * (type code Spec.NONE). + *

+ * None-format is the basis of float.__str__. + *

+ * According to the Python documentation for g-format, the precise rules are as follows: suppose + * that the result formatted with presentation type 'e' and precision p-1 + * would have exponent exp. Then if -4 <= exp < p, the number is formatted with + * presentation type 'f' and precision p-1-exp. Otherwise, the number is + * formatted with presentation type 'e' and precision p-1. In both cases + * insignificant trailing zeros are removed from the significand, and the decimal point is also + * removed if there are no remaining digits following it. + *

+ * The Python documentation says none-format is the same as g-format, but the observed behaviour + * differs from this, in that f-format is only used if -4 <= exp < p-1 (i.e. one + * less), and at least one digit to the right of the decimal point is preserved in the f-format + * (but not the e-format). That behaviour is controlled through the following arguments, with + * these recommended values: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
typeprecisionminFracDigitsexpThresholdAdjexpThreshold
gp00p
#gp-0p
\0p1-1p-1
__str__121-111
+ * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + * @param precision total number of significant digits (precision 0 behaves as 1) + * @param expThresholdAdj +precision = the exponent at which to resume using + * exponential notation + */ + private void format_g(double value, String positivePrefix, int precision, int expThresholdAdj) { + + // Precision 0 behaves as 1 + precision = Math.max(1, precision); + + // Use exponential notation if exponent would be bigger thatn: + int expThreshold = precision + expThresholdAdj; + + if (signAndSpecialNumber(value, positivePrefix)) { + // Finish formatting if zero result. (This is a no-op for nan or inf.) + zeroHelper(precision, expThreshold); + + } else { + + // Convert abs(value) to decimal with p digits of accuracy. + MathContext mc = new MathContext(precision, ROUND_PY); + BigDecimal vv = new BigDecimal(Math.abs(value), mc); + + // This gives us the digits we need for either fixed or exponential format. + String pointlessDigits = vv.unscaledValue().toString(); + + // If we were to complete this as e-format, the exponent would be: + int exp = pointlessDigits.length() - vv.scale() - 1; + + if (-4 <= exp && exp < expThreshold) { + // Finish the job as f-format with variable-precision p-(exp+1). + appendFixed(pointlessDigits, exp, precision); + + } else { + // Finish the job as e-format. + appendExponential(pointlessDigits, exp); + } + } + } + + /** + * Implementation of r-format (float.__repr__) that uses Java's + * {@link Double#toString(double)} to provide conversion and rounding. That method gives us + * almost what we need, but not quite (sometimes it yields 18 digits): here we always round to + * 17 significant digits. Much of the formatting after conversion is shared with + * {@link #format_g(double, String, int, int, int)}. minFracDigits is consulted + * since while float.__repr__ truncates to one digit, within + * complex.__repr__ we truncate fully. + * + * @param value to convert + * @param positivePrefix to use before positive values (e.g. "+") or null to default to "" + */ + private void format_r(double value, String positivePrefix) { + + // Characteristics of repr (precision = 17 and go exponential at 16). + int precision = 17; + int expThreshold = precision - 1; + + if (signAndSpecialNumber(value, positivePrefix)) { + // Finish formatting if zero result. (This is a no-op for nan or inf.) + zeroHelper(precision, expThreshold); + + } else { + + // Generate digit sequence (with no decimal point) with custom rounding. + StringBuilder pointlessBuffer = new StringBuilder(20); + int exp = reprDigits(Math.abs(value), precision, pointlessBuffer); + + if (-4 <= exp && exp < expThreshold) { + // Finish the job as f-format with variable-precision p-(exp+1). + appendFixed(pointlessBuffer, exp, precision); + + } else { + // Finish the job as e-format. + appendExponential(pointlessBuffer, exp); + } + } + } + + /** + * Common code for g-format, none-format and r-format called when the conversion yields "inf", + * "nan" or zero. The method completes formatting of the zero, with the appropriate number of + * decimal places or (in particular circumstances) exponential; notation. + * + * @param precision of conversion (number of significant digits). + * @param expThreshold if zero, causes choice of exponential notation for zero. + */ + private void zeroHelper(int precision, int expThreshold) { + + if (lenMarker == 0) { + // May be 0 or -0 so we still need to ... + if (minFracDigits < 0) { + // In "alternate format", we won't economise trailing zeros. + appendPointAndTrailingZeros(precision - 1); + } else if (lenFraction < minFracDigits) { + // Otherwise, it should be at least the stated minimum length. + appendTrailingZeros(minFracDigits); + } + + // And just occasionally (in none-format) we go exponential even when exp = 0... + if (0 >= expThreshold) { + appendExponent(0); + } + } + } + + /** + * Common code for g-format, none-format and r-format used when the exponent is such that a + * fixed-point presentation is chosen. Normally the method removes trailing digits so as to + * shorten the presentation without loss of significance. This method respects the minimum + * number of fractional digits (digits after the decimal point), in member + * minFracDigits, which is 0 for g-format and 1 for none-format and r-format. When + * minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros + * generated in the conversion are not removed. This supports "%#g" format. + * + * @param digits from converting the value at a given precision. + * @param exp would be the exponent (in e-format), used to position the decimal point. + * @param precision of conversion (number of significant digits). + */ + + private void appendFixed(CharSequence digits, int exp, int precision) { + + // Check for "alternate format", where we won't economise trailing zeros. + boolean noTruncate = (minFracDigits < 0); + + int digitCount = digits.length(); + + if (exp < 0) { + // For a negative exponent, we must insert leading zeros 0.000 ... + result.append("0."); + lenWhole = lenPoint = 1; + for (int i = -1; i > exp; --i) { + result.append('0'); + } + // Then the generated digits (always enough to satisfy no-truncate mode). + result.append(digits); + lenFraction = digitCount - exp - 1; + + } else { + // For a non-negative exponent, it's a question of placing the decimal point. + int w = exp + 1; + if (w < digitCount) { + // There are w whole-part digits + result.append(digits.subSequence(0, w)); + lenWhole = w; + result.append('.').append(digits.subSequence(w, digitCount)); + lenPoint = 1; + lenFraction = digitCount - w; + } else { + // All the digits are whole-part digits. + result.append(digits); + // Just occasionally (in r-format) we need more digits than the precision. + while (digitCount < w) { + result.append('0'); + digitCount += 1; + } + lenWhole = digitCount; + } + + if (noTruncate) { + // Extend the fraction as BigDecimal will have economised on zeros. + appendPointAndTrailingZeros(precision - digitCount); + } + } + + // Finally, ensure we have all and only the fractional digits we should. + if (!noTruncate) { + if (lenFraction < minFracDigits) { + // Otherwise, it should be at least the stated minimum length. + appendTrailingZeros(minFracDigits); + } else { + // And no more + removeTrailingZeros(minFracDigits); + } + } + } + + /** + * Common code for g-format, none-format and r-format used when the exponent is such that an + * exponential presentation is chosen. Normally the method removes trailing digits so as to + * shorten the presentation without loss of significance. Although no minimum number of + * fractional digits is enforced in the exponential presentation, when + * minFracDigits<0 this signifies "no truncation" mode, in which trailing zeros + * generated in the conversion are not removed. This supports "%#g" format. + * + * @param digits from converting the value at a given precision. + * @param exp would be the exponent (in e-format), used to position the decimal point. + */ + private void appendExponential(CharSequence digits, int exp) { + + // The whole-part is the first digit. + result.append(digits.charAt(0)); + lenWhole = 1; + + // And the rest of the digits form the fractional part + int digitCount = digits.length(); + result.append('.').append(digits.subSequence(1, digitCount)); + lenPoint = 1; + lenFraction = digitCount - 1; + + // In no-truncate mode, the fraction is full precision. Otherwise trim it. + if (minFracDigits >= 0) { + // Note minFracDigits only applies to fixed formats. + removeTrailingZeros(0); + } + + // Finally, append the exponent as e+nn. + appendExponent(exp); + } + + /** + * Convert a double to digits and an exponent for use in float.__repr__ (or + * r-format). This method takes advantage of (or assumes) a close correspondence between + * {@link Double#toString(double)} and Python float.__repr__. The correspondence + * appears to be exact, insofar as the Java method produces the minimal non-zero digit string. + * It mostly chooses the same number of digits (and the same digits) as the CPython repr, but in + * a few cases Double.toString produces more digits. This method truncates to the + * number maxDigits, which in practice is always 17. + * + * @param value to convert + * @param maxDigits maximum number of digits to return in buf. + * @param buf for digits of result (recommend size be 20) + * @return the exponent + */ + private static int reprDigits(double value, int maxDigits, StringBuilder buf) { + + // Most of the work is done by Double. + String s = Double.toString(value); + + // Variables for scanning the string + int p = 0, end = s.length(), first = 0, point = end, exp; + char c = 0; + boolean allZero = true; + + // Scan whole part and fractional part digits + while (p < end) { + c = s.charAt(p++); + if (Character.isDigit(c)) { + if (allZero) { + if (c != '0') { + // This is the first non-zero digit. + buf.append(c); + allZero = false; + // p is one *after* the first non-zero digit. + first = p; + } + // Only seen zeros so far: do nothing. + } else { + // We've started, so every digit counts. + buf.append(c); + } + + } else if (c == '.') { + // We remember this location (one *after* '.') to calculate the exponent later. + point = p; + + } else { + // Something after the mantissa. (c=='E' we hope.) + break; + } + } + + // Possibly followed by an exponent. p has already advanced past the 'E'. + if (p < end && c == 'E') { + // If there is an exponent, the mantissa must be in standard form: m.mmmm + assert point == first + 1; + exp = Integer.parseInt(s.substring(p)); + + } else { + // Exponent is based on relationship of decimal point and first non-zero digit. + exp = point - first - 1; + // But that's only correct when the point is to the right (or absent). + if (exp < 0) { + // The point is to the left of the first digit + exp += 1; // = -(first-point) + } + } + + /* + * XXX This still does not round in all the cases it could. I think Java stops generating + * digits when the residual is <= ulp/2. This is to neglect the possibility that the extra + * ulp/2 (before it becomes a different double) could take us to a rounder numeral. To fix + * this, we could express ulp/2 as digits in the same scale as those in the buffer, and + * consider adding them. But Java's behaviour here is probably a manifestation of bug + * JDK-4511638. + */ + + // Sometimes the result is more digits than we want for repr. + if (buf.length() > maxDigits) { + // Chop the trailing digits, remembering the most significant lost digit. + int d = buf.charAt(maxDigits); + buf.setLength(maxDigits); + // We round half up. Not absolutely correct since Double has already rounded. + if (d >= '5') { + // Treat this as a "carry one" into the numeral buf[0:maxDigits]. + for (p = maxDigits - 1; p >= 0; p--) { + // Each pass of the loop does one carry from buf[p+1] to buf[p]. + d = buf.charAt(p) + 1; + if (d <= '9') { + // Carry propagation stops here. + buf.setCharAt(p, (char)d); + break; + } else { + // 9 + 1 -> 0 carry 1. Keep looping. + buf.setCharAt(p, '0'); + } + } + if (p < 0) { + /* + * We fell off the bottom of the buffer with one carry still to propagate. You + * may expect: buf.insert(0, '1') here, but note that every digit in + * buf[0:maxDigits] is currently '0', so all we need is: + */ + buf.setCharAt(0, '1'); + exp += 1; + } + } + } + + return exp; + } + + /** + * Append the trailing fractional zeros, as required by certain formats, so that the total + * number of fractional digits is no less than specified. If minFracDigits<=0, + * the method leaves the {@link #result} buffer unchanged. + * + * @param minFracDigits smallest number of fractional digits on return + */ + private void appendTrailingZeros(int minFracDigits) { + + int f = lenFraction; + + if (minFracDigits > f) { + if (lenPoint == 0) { + // First need to add a decimal point. (Implies lenFraction=0.) + result.append('.'); + lenPoint = 1; + } + + // Now make up the required number of zeros. + for (; f < minFracDigits; f++) { + result.append('0'); + } + lenFraction = f; + } + } + + /** + * Append the trailing fractional zeros, as required by certain formats, so that the total + * number of fractional digits is no less than specified. If there is no decimal point + * originally (and therefore no fractional part), the method will add a decimal point, even if + * it adds no zeros. + * + * @param minFracDigits smallest number of fractional digits on return + */ + private void appendPointAndTrailingZeros(int minFracDigits) { + + if (lenPoint == 0) { + // First need to add a decimal point. (Implies lenFraction=0.) + result.append('.'); + lenPoint = 1; + } + + // Now make up the required number of zeros. + int f; + for (f = lenFraction; f < minFracDigits; f++) { + result.append('0'); + } + lenFraction = f; + } + + /** + * Remove trailing zeros from the fractional part, as required by certain formats, leaving at + * least the number of fractional digits specified. If the resultant number of fractional digits + * is zero, this method will also remove the trailing decimal point (if there is one). + * + * @param minFracDigits smallest number of fractional digits on return + */ + private void removeTrailingZeros(int minFracDigits) { + + int f = lenFraction; + + if (lenPoint > 0) { + // There's a decimal point at least, and there may be some fractional digits. + if (minFracDigits == 0 || f > minFracDigits) { + + int fracStart = result.length() - f; + for (; f > minFracDigits; --f) { + if (result.charAt(fracStart - 1 + f) != '0') { + // Keeping this one as it isn't a zero + break; + } + } + + // f is now the number of fractional digits we wish to retain. + if (f == 0 && lenPoint > 0) { + // We will be stripping all the fractional digits. Take the decimal point too. + lenPoint = lenFraction = 0; + f = -1; + } else { + lenFraction = f; + } + + // Snip the characters we are going to remove (if any). + if (fracStart + f < result.length()) { + result.setLength(fracStart + f); + } + } + } + } + + /** + * Append the current value of {@code exp} in the format "e{:+02d}" (for example + * e+05, e-10, e+308 , etc.). + * + * @param exp exponent value to append + */ + private void appendExponent(int exp) { + + int marker = result.length(); + String e; + + // Deal with sign and leading-zero convention by explicit tests. + if (exp < 0) { + e = (exp <= -10) ? "e-" : "e-0"; + exp = -exp; + } else { + e = (exp < 10) ? "e+0" : "e+"; + } + + result.append(e).append(exp); + lenMarker = 1; + lenExponent = result.length() - marker - 1; + } + + /** + * Return the index in {@link #result} of the first letter. helper for {@link #uppercase()} and + * {@link #getExponent()} + */ + private int indexOfMarker() { + return start + lenSign + lenWhole + lenPoint + lenFraction; + } + +} diff --git a/src/org/python/core/stringlib/Formatter.java b/src/org/python/core/stringlib/Formatter.java deleted file mode 100644 --- a/src/org/python/core/stringlib/Formatter.java +++ /dev/null @@ -1,247 +0,0 @@ -package org.python.core.stringlib; -import org.python.core.*; -import org.python.core.util.ExtraMath; - -import java.math.BigInteger; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; - - -public class Formatter { - - public static String formatFloat(double value, InternalFormatSpec spec) { - InternalFormatter f = new InternalFormatter(spec); - String string = f.format(value); - return spec.pad(string, '>', 0); - } - - public static String formatComplex(double real, double imag, InternalFormatSpec spec) { - String string; - InternalFormatter f = new InternalFormatter(spec); - String r = f.format(real); - String i = f.format(imag); - if (i.charAt(0) == '-') { - string = r + i + "j"; - } else { - string = r + "+" + i + "j"; - } - return spec.pad(string, '>', 0); - } -} - -//Adapted from PyString's StringFormatter class. -final class InternalFormatter { - InternalFormatSpec spec; - boolean negative; - int precision; - - public InternalFormatter(InternalFormatSpec spec) { - this.spec = spec; - this.precision = spec.precision; - if (this.precision == -1) - this.precision = 6; - } - - private void checkPrecision(String type) { - if(precision > 250) { - // A magic number. Larger than in CPython. - throw Py.OverflowError("formatted " + type + " is too long (precision too long?)"); - } - - } - - private String formatExp(long v, int radix) { - checkPrecision("integer"); - if (v < 0) { - negative = true; - v = -v; - } - String s = Long.toString(v, radix); - while (s.length() < 2) { - s = "0"+s; - } - return s; - } - - static class DecimalFormatTemplate { - static DecimalFormat template; - static { - template = new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US)); - DecimalFormatSymbols symbols = template.getDecimalFormatSymbols(); - symbols.setNaN("nan"); - symbols.setInfinity("inf"); - template.setDecimalFormatSymbols(symbols); - template.setGroupingUsed(false); - } - } - - private static final DecimalFormat getDecimalFormat() { - return (DecimalFormat)DecimalFormatTemplate.template.clone(); - } - - static class PercentageFormatTemplate { - static DecimalFormat template; - static { - template = new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US)); - DecimalFormatSymbols symbols = template.getDecimalFormatSymbols(); - symbols.setNaN("nan"); - symbols.setInfinity("inf"); - template.setDecimalFormatSymbols(symbols); - template.setGroupingUsed(false); - } - } - - private static final DecimalFormat getPercentageFormat() { - return (DecimalFormat)PercentageFormatTemplate.template.clone(); - } - - private String formatFloatDecimal(double v, boolean truncate) { - checkPrecision("decimal"); - if (v < 0) { - v = -v; - negative = true; - } - - DecimalFormat decimalFormat = getDecimalFormat(); - decimalFormat.setMaximumFractionDigits(precision); - decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision); - - if (spec.thousands_separators) { - decimalFormat.setGroupingUsed(true); - } - String ret = decimalFormat.format(v); - return ret; - } - - private String formatPercentage(double v, boolean truncate) { - checkPrecision("decimal"); - if (v < 0) { - v = -v; - negative = true; - } - - DecimalFormat decimalFormat = getPercentageFormat(); - decimalFormat.setMaximumFractionDigits(precision); - decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision); - - String ret = decimalFormat.format(v); - return ret; - } - - private String formatFloatExponential(double v, char e, boolean truncate) { - StringBuilder buf = new StringBuilder(); - boolean isNegative = false; - if (v < 0) { - v = -v; - isNegative = true; - } - double power = 0.0; - if (v > 0) - power = ExtraMath.closeFloor(Math.log10(v)); - String exp = formatExp((long)power, 10); - if (negative) { - negative = false; - exp = '-'+exp; - } - else { - exp = '+' + exp; - } - - double base = v/Math.pow(10, power); - buf.append(formatFloatDecimal(base, truncate)); - buf.append(e); - - buf.append(exp); - negative = isNegative; - - return buf.toString(); - } - - @SuppressWarnings("fallthrough") - public String format(double value) { - String string; - - if (spec.alternate) { - throw Py.ValueError("Alternate form (#) not allowed in float format specifier"); - } - int sign = Double.compare(value, 0.0d); - - if (Double.isNaN(value)) { - if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') { - string = "NAN"; - } else { - string = "nan"; - } - } else if (Double.isInfinite(value)) { - if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') { - if (value > 0) { - string = "INF"; - } else { - string = "-INF"; - } - } else { - if (value > 0) { - string = "inf"; - } else { - string = "-inf"; - } - } - } else { - - switch(spec.type) { - case 'e': - case 'E': - string = formatFloatExponential(value, spec.type, false); - if (spec.type == 'E') { - string = string.toUpperCase(); - } - break; - case 'f': - case 'F': - string = formatFloatDecimal(value, false); - if (spec.type == 'F') { - string = string.toUpperCase(); - } - break; - case 'g': - case 'G': - int exponent = (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value))); - int origPrecision = precision; - if (exponent >= -4 && exponent < precision) { - precision -= exponent + 1; - string = formatFloatDecimal(value, !spec.alternate); - } else { - // Exponential precision is the number of digits after the decimal - // point, whereas 'g' precision is the number of significant digits -- - // and exponential always provides one significant digit before the - // decimal point - precision--; - string = formatFloatExponential(value, (char)(spec.type-2), !spec.alternate); - } - if (spec.type == 'G') { - string = string.toUpperCase(); - } - precision = origPrecision; - break; - case '%': - string = formatPercentage(value, false); - break; - default: - //Should never get here, since this was checked in PyFloat. - throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'", - spec.type)); - } - } - if (sign >= 0) { - if (spec.sign == '+') { - string = "+" + string; - } else if (spec.sign == ' ') { - string = " " + string; - } - } - if (sign < 0 && string.charAt(0) != '-') { - string = "-" + string; - } - return string; - } -} diff --git a/src/org/python/core/stringlib/InternalFormat.java b/src/org/python/core/stringlib/InternalFormat.java new file mode 100644 --- /dev/null +++ b/src/org/python/core/stringlib/InternalFormat.java @@ -0,0 +1,824 @@ +// Copyright (c) Jython Developers +package org.python.core.stringlib; + +import org.python.core.Py; +import org.python.core.PyException; + +public class InternalFormat { + + /** + * Create a {@link Spec} object by parsing a format specification. + * + * @param text to parse + * @return parsed equivalent to text + */ + public static Spec fromText(String text) { + Parser parser = new Parser(text); + return parser.parse(); + } + + /** + * A class that provides the base for implementations of type-specific formatting. In a limited + * way, it acts like a StringBuilder to which text and one or more numbers may be appended, + * formatted according to the format specifier supplied at construction. These are ephemeral + * objects that are not, on their own, thread safe. + */ + public static class Formatter implements Appendable { + + /** The specification according to which we format any number supplied to the method. */ + protected final Spec spec; + /** The (partial) result. */ + protected StringBuilder result; + + /** The number we are working on floats at the end of the result, and starts here. */ + protected int start; + /** If it contains no sign, this length is zero, and 1 otherwise. */ + protected int lenSign; + /** The length of the whole part (to left of the decimal point or exponent) */ + protected int lenWhole; + + /** + * Construct the formatter from a specification and initial buffer capacity. A reference is + * held to this specification, but it will not be modified by the actions of this class. + * + * @param spec parsed conversion specification + * @param width of buffer initially + */ + public Formatter(Spec spec, int width) { + this.spec = spec; + result = new StringBuilder(width); + } + + /** + * Current (possibly final) result of the formatting, as a String. + * + * @return formatted result + */ + public String getResult() { + return result.toString(); + } + + /* + * Implement Appendable interface by delegation to the result buffer. + * + * @see java.lang.Appendable#append(char) + */ + @Override + public Formatter append(char c) { + result.append(c); + return this; + } + + @Override + public Formatter append(CharSequence csq) { + result.append(csq); + return this; + } + + @Override + public Formatter append(CharSequence csq, int start, int end) // + throws IndexOutOfBoundsException { + result.append(csq, start, end); + return this; + } + + /** + * Clear the instance variables describing the latest object in {@link #result}, ready to + * receive a new number + */ + public void setStart() { + // Mark the end of the buffer as the start of the current object and reset all. + start = result.length(); + // Clear the variable describing the latest number in result. + reset(); + } + + /** + * Clear the instance variables describing the latest object in {@link #result}, ready to + * receive a new one. + */ + protected void reset() { + // Clear the variable describing the latest object in result. + lenSign = lenWhole = 0; + } + + /** + * Supports {@link #toString()} by returning the lengths of the successive sections in the + * result buffer, used for navigation relative to {@link #start}. The toString + * method shows a '|' character between each section when it prints out the buffer. Override + * this when you define more lengths in the subclass. + * + * @return + */ + protected int[] sectionLengths() { + return new int[] {lenSign, lenWhole}; + } + + /** + * {@inheritDoc} + *

+ * Overridden to provide a debugging view in which the actual text is shown divided up by + * the len* member variables. If the dividers don't look right, those variables + * have not remained consistent with the text. + */ + @Override + public String toString() { + if (result == null) { + return ("[]"); + } else { + StringBuilder buf = new StringBuilder(result.length() + 20); + buf.append(result); + try { + int p = start; + buf.insert(p++, '['); + for (int len : sectionLengths()) { + p += len; + buf.insert(p++, '|'); + } + buf.setCharAt(p - 1, ']'); + } catch (IndexOutOfBoundsException e) { + // Some length took us beyond the end of the result buffer. Pass. + } + return buf.toString(); + } + } + + /** + * Insert grouping characters (conventionally commas) into the whole part of the number. + * {@link #lenWhole} will increase correspondingly. + * + * @param groupSize normally 3. + * @param comma or some other character to use as a separator. + */ + protected void groupDigits(int groupSize, char comma) { + + // Work out how many commas (or whatever) it takes to group the whole-number part. + int commasNeeded = (lenWhole - 1) / groupSize; + + if (commasNeeded > 0) { + // Index *just after* the current last digit of the whole part of the number. + int from = start + lenSign + lenWhole; + // Open a space into which the whole part will expand. + makeSpaceAt(from, commasNeeded); + // Index *just after* the end of that space. + int to = from + commasNeeded; + // The whole part will be longer by the number of commas to be inserted. + lenWhole += commasNeeded; + + /* + * Now working from high to low, copy all the digits that have to move. Each pass + * copies one group and inserts a comma, which makes the to-pointer move one place + * extra. The to-pointer descends upon the from-pointer from the right. + */ + while (to > from) { + // Copy a group + for (int i = 0; i < groupSize; i++) { + result.setCharAt(--to, result.charAt(--from)); + } + // Write the comma that precedes it. + result.setCharAt(--to, comma); + } + } + } + + /** + * Make a space in {@link #result} of a certain size and position. On return, the segment + * lengths are likely to be invalid until the caller adjusts them corresponding to the + * insertion. There is no guarantee what the opened space contains. + * + * @param pos at which to make the space + * @param size of the space + */ + protected void makeSpaceAt(int pos, int size) { + int n = result.length(); + if (pos < n) { + // Space is not at the end: must copy what's to the right of pos. + String tail = result.substring(pos); + result.setLength(n + size); + result.replace(pos + size, n + size, tail); + } else { + // Space is at the end. + result.setLength(n + size); + } + } + + /** + * Convert letters in the representation of the current number (in {@link #result}) to upper + * case. + */ + protected void uppercase() { + int end = result.length(); + for (int i = start; i < end; i++) { + char c = result.charAt(i); + result.setCharAt(i, Character.toUpperCase(c)); + } + } + + /** + * Pad the result so far (defined as the entire contents of {@link #result}) using the + * alignment, target width and fill character defined in {@link #spec}. The action of + * padding will increase the overall length of the result to the target width, if that is + * greater than the current length. + *

+ * When the padding method has decided that that it needs to add n padding characters, it + * will affect {@link #start} or {@link #lenSign} as follows. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
alignmeaningstartlenSignresult.length()
<left-aligned+0+0+n
>right-aligned+n+0+n
^centred+(n/2)+0+n
=pad after sign+0+n+n
+ * Note that we may have converted more than one value into the result buffer (for example + * when formatting a complex number). The pointer start is at the start of the + * last number converted. Padding with zeros, and the "pad after sign" mode, will produce a + * result you probably don't want. It is up to the client to disallow this (which + * complex does). + * + * @param value to pad + * @return this object + */ + public Formatter pad() { + + // We'll need this many pad characters (if>0). Note Spec.UNDEFINED<0. + int n = spec.width - result.length(); + if (n > 0) { + + char align = spec.getAlign('>'); // Right for numbers (wrong for strings) + char fill = spec.getFill(' '); + + // Start by assuming padding is all leading ('>' case or '=') + int leading = n; + + // Split the total padding according to the alignment + if (align == '^') { + // Half the padding before + leading = n / 2; + } else if (align == '<') { + // All the padding after + leading = 0; + } + + // All padding that is not leading is trailing + int trailing = n - leading; + + // Insert the leading space + if (leading > 0) { + int pos; + if (align == '=') { + // Incorporate into the (latest) whole part + pos = start + lenSign; + lenWhole += leading; + } else { + // Insert at the very beginning (not start) by default. + pos = 0; + start += leading; + } + makeSpaceAt(pos, leading); + for (int i = 0; i < leading; i++) { + result.setCharAt(pos + i, fill); + } + } + + // Append the trailing space + for (int i = 0; i < trailing; i++) { + result.append(fill); + } + + // Check for special case + if (align == '=' && fill == '0' && spec.grouping) { + // We must extend the grouping separator into the padding + zeroPadAfterSignWithGroupingFixup(3, ','); + } + } + + return this; + } + + /** + * Fix-up the zero-padding of the last formatted number in {@link #result()} in the special + * case where a sign-aware padding ({@link #spec}.align='=') was requested, the + * fill character is '0', and the digits are to be grouped. In these exact + * circumstances, the grouping, which must already have been applied to the (whole part) + * number itself, has to be extended into the zero-padding. + * + *

+         * >>> format(-12e8, " =30,.3f")
+         * '-            1,200,000,000.000'
+         * >>> format(-12e8, "*=30,.3f")
+         * '-************1,200,000,000.000'
+         * >>> format(-12e8, "*>30,.3f")
+         * '************-1,200,000,000.000'
+         * >>> format(-12e8, "0>30,.3f")
+         * '000000000000-1,200,000,000.000'
+         * >>> format(-12e8, "0=30,.3f")
+         * '-0,000,000,001,200,000,000.000'
+         * 
+ * + * The padding has increased the overall length of the result to the target width. About one + * in three call to this method adds one to the width, because the whole part cannot start + * with a comma. + * + *
+         * >>> format(-12e8, " =30,.4f")
+         * '-           1,200,000,000.0000'
+         * >>> format(-12e8, "0=30,.4f")
+         * '-0,000,000,001,200,000,000.0000'
+         * 
+ * + * Insert grouping characters (conventionally commas) into the whole part of the number. + * {@link #lenWhole} will increase correspondingly. + * + * @param groupSize normally 3. + * @param comma or some other character to use as a separator. + */ + protected void zeroPadAfterSignWithGroupingFixup(int groupSize, char comma) { + /* + * Suppose the format call was format(-12e8, "0=30,.3f"). At this point, we have + * something like this in result: .. [-|0000000000001,200,000,000|.|000||] + * + * All we need do is over-write some of the zeros with the separator comma, in the + * portion marked as the whole-part: [-|0,000,000,001,200,000,000|.|000||] + */ + + // First digit of the whole-part. + int firstZero = start + lenSign; + // One beyond last digit of the whole-part. + int p = firstZero + lenWhole; + // Step back down the result array visiting the commas. (Easiest to do all of them.) + int step = groupSize + 1; + for (p = p - step; p >= firstZero; p -= step) { + result.setCharAt(p, comma); + } + + // Sometimes the last write was exactly at the first padding zero. + if (p + step == firstZero) { + /* + * Suppose the format call was format(-12e8, "0=30,.4f"). At the beginning, we had + * something like this in result: . [-|000000000001,200,000,000|.|0000||] + * + * And now, result looks like this: [-|0000,000,001,200,000,000|.|0000||] in which + * the first zero is wrong as it stands, nor can it just be over-written with a + * comma. We have to insert another zero, even though this makes the result longer + * than we were given. + */ + result.insert(firstZero, '0'); + lenWhole += 1; + } + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting: + *

+ * "Unknown format code '"+code+"' for object of type '"+forType+"'" + * + * @param code the presentation type + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException unknownFormat(char code, String forType) { + String msg = "Unknown format code '" + code + "' for object of type '" + forType + "'"; + return Py.ValueError(msg); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that alternate form is not + * allowed in a format specifier for the named type. + * + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException alternateFormNotAllowed(String forType) { + return notAllowed("Alternate form (#)", forType); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that the given alignment + * flag is not allowed in a format specifier for the named type. + * + * @param align type of alignment + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException alignmentNotAllowed(char align, String forType) { + return notAllowed("'" + align + "' alignment flag", forType); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that zero padding is not + * allowed in a format specifier for the named type. + * + * @param forType the type it was found applied to + * @return exception to throw + */ + public static PyException zeroPaddingNotAllowed(String forType) { + return notAllowed("Zero padding", forType); + } + + /** + * Convenience method returning a {@link Py#ValueError} reporting that some format specifier + * feature is not allowed for the named type. + * + * @param particularOutrage committed in the present case + * @param forType the type it where it is an outrage + * @return exception to throw + */ + protected static PyException notAllowed(String particularOutrage, String forType) { + String msg = particularOutrage + " is not allowed in " + forType + " format specifier"; + return Py.ValueError(msg); + } + + } + + /** + * Parsed PEP-3101 format specification of a single field, encapsulating the format for use by + * formatting methods. This class holds the several attributes that might be decoded from a + * format specifier. Each attribute has a reserved value used to indicate "unspecified". + * Spec objects may be merged such that one Spec provides values, + * during the construction of a new Spec, for attributes that are unspecified in a + * primary source. + *

+ * This structure is returned by factory method {@link #fromText(CharSequence)}, and having + * public final members is freely accessed by formatters such as {@link FloatBuilder}, and the + * __format__ methods of client object types. + *

+ * The fields correspond to the elements of a format specification. The grammar of a format + * specification is: + * + *

+     * [[fill]align][sign][#][0][width][,][.precision][type]
+     * 
+ * + * A typical idiom is: + * + *
+     *     private static final InternalFormatSpec FLOAT_DEFAULT = InternalFormatSpec.from(">");
+     *     ...
+     *         InternalFormatSpec spec = InternalFormatSpec.from(specString, FLOAT_DEFAULT);
+     *         ... // Validation of spec.type, and other attributes, for this type.
+     *         FloatBuilder buf = new FloatBuilder(spec);
+     *         buf.format(value);
+     *         String result = buf.getResult();
+     *
+     * 
+ */ + public static class Spec { + + /** The fill character specified, or '\uffff' if unspecified. */ + public final char fill; + /** + * Alignment indicator is one of {'<', '^', '>', '=', or '\uffff' if + * unspecified. + */ + public final char align; + /** + * Sign-handling flag, one of '+', '-', or ' ', or + * '\uffff' if unspecified. + */ + public final char sign; + /** The alternative format flag '#' was given. */ + public final boolean alternate; + /** Width to which to pad the result, or -1 if unspecified. */ + public final int width; + /** Insert the grouping separator (which in Python always indicates a group-size of 3). */ + public final boolean grouping; + /** Precision decoded from the format, or -1 if unspecified. */ + public final int precision; + /** Type key from the format, or '\uffff' if unspecified. */ + public final char type; + + /** Non-character code point used to represent "no value" in char attributes. */ + public static final char NONE = '\uffff'; + /** Negative value used to represent "no value" in int attributes. */ + public static final int UNSPECIFIED = -1; + + /** + * Test to see if an attribute has been specified. + * + * @param c attribute + * @return true only if the attribute is not equal to {@link #NONE} + */ + public static final boolean specified(char c) { + return c != NONE; + } + + /** + * Test to see if an attribute has been specified. + * + * @param value of attribute + * @return true only if the attribute is >=0 (meaning that it has been specified). + */ + public static final boolean specified(int value) { + return value >= 0; + } + + /** + * Constructor to set all the fields in the format specifier. + * + *
+         * [[fill]align][sign][#][0][width][,][.precision][type]
+         * 
+ * + * @param fill fill character (or {@link #NONE} + * @param align alignment indicator, one of {'<', '^', '>', '=' + * @param sign policy, one of '+', '-', or ' '. + * @param alternate true to request alternate formatting mode ('#' flag). + * @param width of field after padding or -1 to default + * @param grouping true to request comma-separated groups + * @param precision (e.g. decimal places) or -1 to default + * @param type indicator character + */ + public Spec(char fill, char align, char sign, boolean alternate, int width, + boolean grouping, int precision, char type) { + this.fill = fill; + this.align = align; + this.sign = sign; + this.alternate = alternate; + this.width = width; + this.grouping = grouping; + this.precision = precision; + this.type = type; + } + + /** + * Return a format specifier (text) equivalent to the value of this Spec. + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + if (specified(fill)) { + buf.append(fill); + } + if (specified(align)) { + buf.append(align); + } + if (specified(sign)) { + buf.append(sign); + } + if (alternate) { + buf.append('#'); + } + if (specified(width)) { + buf.append(width); + } + if (grouping) { + buf.append(','); + } + if (specified(precision)) { + buf.append('.').append(precision); + } + if (specified(type)) { + buf.append(type); + } + return buf.toString(); + } + + /** + * Return a merged Spec object, in which any attribute of this object, that is + * specified (or true) has the same value in the result, and any attribute of + * this object that is unspecified (or false) has the value that attribute + * takes in the other object. This the second object supplies default values. (These + * defaults may also be unspecified.) The use of this method is to allow a Spec + * constructed from text to record exactly, and only, what was in the textual specification, + * while the __format__ method of a client object supplies its type-specific defaults. Thus + * "20" means "<20s" to a str, ">20.12" to a float and ">20.12g" + * to a complex. + * + * @param defaults to merge where this object does not specify the attribute. + * @return a new Spec object. + */ + public Spec withDefaults(Spec other) { + return new Spec(// + specified(fill) ? fill : other.fill, // + specified(align) ? align : other.align, // + specified(sign) ? sign : other.sign, // + alternate || other.alternate, // + specified(width) ? width : other.width, // + grouping || other.grouping, // + specified(precision) ? precision : other.precision, // + specified(type) ? type : other.type // + ); + } + + /** + * Defaults applicable to most numeric types. Equivalent to " >" + */ + public static final Spec NUMERIC = new Spec(' ', '>', Spec.NONE, false, Spec.UNSPECIFIED, + false, Spec.UNSPECIFIED, Spec.NONE); + + /** + * Constructor offering just precision and type. + * + *
+         * [.precision][type]
+         * 
+ * + * @param precision (e.g. decimal places) + * @param type indicator character + */ + public Spec(int width, int precision, char type) { + this(' ', '>', Spec.NONE, false, UNSPECIFIED, false, precision, type); + } + + /** The alignment from the parsed format specification, or default. */ + public char getFill(char defaultFill) { + return specified(fill) ? fill : defaultFill; + } + + /** The alignment from the parsed format specification, or default. */ + public char getAlign(char defaultAlign) { + return specified(align) ? align : defaultAlign; + } + + /** The precision from the parsed format specification, or default. */ + public int getPrecision(int defaultPrecision) { + return specified(precision) ? precision : defaultPrecision; + } + + /** The type code from the parsed format specification, or default supplied. */ + public char getType(char defaultType) { + return specified(type) ? type : defaultType; + } + + } + + /** + * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} + * method that translates the format specification into an Spec object. + */ + private static class Parser { + + private String spec; + private int ptr; + + /** + * Constructor simply holds the specification string ahead of the {@link #parse()} + * operation. + * + * @param spec format specifier to parse (e.g. "<+12.3f") + */ + Parser(String spec) { + this.spec = spec; + this.ptr = 0; + } + + /** + * Parse the specification with which this object was initialised into an {@link Spec}, + * which is an object encapsulating the format for use by formatting methods. This parser + * deals only with the format specifiers themselves, as accepted by the + * __format__ method of a type, or the format() built-in, not + * format strings in general as accepted by str.format(). + * + * @return the Spec equivalent to the string given. + */ + /* + * This method is the equivalent of CPython's parse_internal_render_format_spec() in + * ~/Objects/stringlib/formatter.h, but we deal with defaults another way. + */ + Spec parse() { + + char fill = Spec.NONE, align = Spec.NONE; + char sign = Spec.NONE, type = Spec.NONE; + boolean alternate = false, grouping = false; + int width = Spec.UNSPECIFIED, precision = Spec.UNSPECIFIED; + + // Scan [[fill]align] ... + if (isAlign()) { + // First is alignment. fill not specified. + align = spec.charAt(ptr++); + } else { + // Peek ahead + ptr += 1; + if (isAlign()) { + // Second character is alignment, so first is fill + fill = spec.charAt(0); + align = spec.charAt(ptr++); + } else { + // Second character is not alignment. We are still at square zero. + ptr = 0; + } + } + + // Scan [sign] ... + if (isAt("+- ")) { + sign = spec.charAt(ptr++); + } + + // Scan [#] ... + alternate = scanPast('#'); + + // Scan [0] ... + if (scanPast('0')) { + // Accept 0 here as equivalent to zero-fill but only not set already. + if (!Spec.specified(fill)) { + fill = '0'; + if (!Spec.specified(align)) { + // Also accept it as equivalent to "=" aligment but only not set already. + align = '='; + } + } + } + + // Scan [width] + if (isDigit()) { + width = scanInteger(); + } + + // Scan [,][.precision][type] + grouping = scanPast(','); + + // Scan [.precision] + if (scanPast('.')) { + if (isDigit()) { + precision = scanInteger(); + } else { + throw new IllegalArgumentException("Format specifier missing precision"); + } + } + + // Scan [type] + if (ptr < spec.length()) { + type = spec.charAt(ptr++); + } + + // If we haven't reached the end, something is wrong + if (ptr != spec.length()) { + throw new IllegalArgumentException("Invalid conversion specification"); + } + + // Restrict grouping to known formats. (Mirrors CPython, but misplaced?) + if (grouping && "defgEG%F\0".indexOf(type) == -1) { + throw new IllegalArgumentException("Cannot specify ',' with '" + type + "'."); + } + + // Create a specification + return new Spec(fill, align, sign, alternate, width, grouping, precision, type); + } + + /** Test that the next character is exactly the one specified, and advance past it if it is. */ + private boolean scanPast(char c) { + if (ptr < spec.length() && spec.charAt(ptr) == c) { + ptr++; + return true; + } else { + return false; + } + } + + /** Test that the next character is one of a specified set. */ + private boolean isAt(String chars) { + return ptr < spec.length() && (chars.indexOf(spec.charAt(ptr)) >= 0); + } + + /** Test that the next character is one of the alignment characters. */ + private boolean isAlign() { + return ptr < spec.length() && ("<^>=".indexOf(spec.charAt(ptr)) >= 0); + } + + /** Test that the next character is a digit. */ + private boolean isDigit() { + return ptr < spec.length() && Character.isDigit(spec.charAt(ptr)); + } + + /** The current character is a digit (maybe a sign). Scan the integer, */ + private int scanInteger() { + int p = ptr++; + while (isDigit()) { + ptr++; + } + return Integer.parseInt(spec.substring(p, ptr)); + } + + } + +} diff --git a/src/org/python/core/stringlib/InternalFormatSpec.java b/src/org/python/core/stringlib/InternalFormatSpec.java --- a/src/org/python/core/stringlib/InternalFormatSpec.java +++ b/src/org/python/core/stringlib/InternalFormatSpec.java @@ -1,42 +1,88 @@ package org.python.core.stringlib; /** - * Parsed PEP-3101 format specification of a single field. + * Parsed PEP-3101 format specification of a single field. This class holds the several attributes + * that might be decoded from a format specifier. It provides a method + * {@link #pad(String, char, int)} for adjusting a string using those attributes related to padding + * to a string assumed to be the result of formatting to the given precision. + *

+ * This structure is returned by {@link InternalFormatSpecParser#parse()} and having public members + * is freely used by {@link InternalFormatSpecParser}, {@link Formatter} and the __format__ methods + * of client object types. + *

+ * The fields correspond to the elements of a format specification. The grammar of a format + * specification is: + * + *

+ * [[fill]align][sign][#][0][width][,][.precision][type]
+ * 
*/ public final class InternalFormatSpec { + + /** The fill specified in the grammar. */ public char fill_char; + /** Alignment indicator is 0, or one of {'<', '^', '>', '=' . */ public char align; + /** The alternative format flag '#' was given. */ public boolean alternate; + /** Sign-handling flag, one of '+', '-', or ' '. */ public char sign; + /** Width to which to pad the resault in {@link #pad(String, char, int)}. */ public int width = -1; + /** Insert the grouping separator (which in Python always indicates a group-size of 3). */ public boolean thousands_separators; + /** Precision decoded from the format. */ public int precision = -1; + /** Type key from the format. */ public char type; + /** + * Pad value, using {@link #fill_char} (or ' ') before and after, to {@link #width} + * -leaveWidth, aligned according to {@link #align} (or according to + * defaultAlign). + * + * @param value to pad + * @param defaultAlign to use if this.align=0 (one of '<', + * '^', '>', or '='). + * @param leaveWidth to reduce effective this.width by + * @return + */ public String pad(String value, char defaultAlign, int leaveWidth) { + + // We'll need this many pad characters (if>0) int remaining = width - value.length() - leaveWidth; if (remaining <= 0) { return value; } - StringBuilder result = new StringBuilder(); - int leading = remaining; + + // Use this.align or defaultAlign int useAlign = align; if (useAlign == 0) { useAlign = defaultAlign; } + + // By default all padding is leading padding ('<' case or '=') + int leading = remaining; if (useAlign == '^') { - leading = remaining/2; + // Half the padding before + leading = remaining / 2; } else if (useAlign == '<') { + // All the padding after leading = 0; } + + // Now build the result + StringBuilder result = new StringBuilder(); char fill = fill_char != 0 ? fill_char : ' '; - for (int i = 0; i < leading; i++) { + + for (int i = 0; i < leading; i++) { // before result.append(fill); } result.append(value); - for (int i = 0; i < remaining - leading; i++) { + for (int i = 0; i < remaining - leading; i++) { // after result.append(fill); } + return result.toString(); } } diff --git a/src/org/python/core/stringlib/InternalFormatSpecParser.java b/src/org/python/core/stringlib/InternalFormatSpecParser.java --- a/src/org/python/core/stringlib/InternalFormatSpecParser.java +++ b/src/org/python/core/stringlib/InternalFormatSpecParser.java @@ -1,19 +1,26 @@ package org.python.core.stringlib; /** - * Parser for PEP-3101 field format specifications. + * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} method + * that translates the format specification into an InternalFormatSpec object. */ public class InternalFormatSpecParser { + private String spec; private int index; + /** + * Constructor simply holds the specification streang ahead of the {@link #parse()} operation. + * + * @param spec format specifier to parse (e.g. "<+12.3f") + */ public InternalFormatSpecParser(String spec) { this.spec = spec; this.index = 0; } private static boolean isAlign(char c) { - switch(c) { + switch (c) { case '<': case '>': case '=': @@ -24,6 +31,24 @@ } } + /** + * Parse the specification with which this object was initialised into an + * {@link InternalFormatSpec}, which is an object encapsulating the format for use by formatting + * methods. This parser deals only with the format specifiers themselves, as accepted by the + * __format__ method of a type, or the format() built-in, not format + * strings in general as accepted by str.format(). A typical idiom is: + * + *
+     * InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
+     * 
+ * + * @return the InternalFormatSpec equivalent to the constructor argument + */ + /* + * This method is the equivalent of CPython's parse_internal_render_format_spec() in + * ~/Objects/stringlib/formatter.h. + */ + // XXX Better encapsulated as a constructor of InternalFormatSpec? public InternalFormatSpec parse() { InternalFormatSpec result = new InternalFormatSpec(); if (spec.length() >= 1 && isAlign(spec.charAt(0))) { diff --git a/src/org/python/core/util/ExtraMath.java b/src/org/python/core/util/ExtraMath.java --- a/src/org/python/core/util/ExtraMath.java +++ b/src/org/python/core/util/ExtraMath.java @@ -1,6 +1,9 @@ // Copyright (c) Corporation for National Research Initiatives package org.python.core.util; +import java.math.BigDecimal; +import java.math.RoundingMode; + /** * A static utility class with two additional math functions. */ @@ -26,11 +29,58 @@ } /** - * Returns floor(v) except when v is very close to the next number, when it - * returns ceil(v); + * Returns floor(v) except when v is very close to the next number, when it returns ceil(v); */ public static double closeFloor(double v) { double floor = Math.floor(v); return close(v, floor + 1.0) ? floor + 1.0 : floor; } + + /** + * Round the argument x to n decimal places. (Rounding is half-up in Python 2.) The method uses + * BigDecimal, to compute r(x*10n)*10-n, where r() round to + * the nearest integer. It takes some short-cuts for extreme values. + *

+ * For sufficiently small x*10n, the rounding is to zero, and the return value + * is a signed zero (same sign as x). Suppose x = a*2b, where the significand + * we must have a<2. Sufficiently small means such that n log210 < + * -(b+2). + *

+ * For sufficiently large x*10n, the adjustment of rounding is too small to + * affect the least significant bit. That is a*2b represents an amount greater + * than one, and rounding no longer affects the value, and the return is x. Since the matissa + * has 52 fractional bits, sufficiently large means such that n log210 > 52-b. + * + * @param x to round + * @param n decimal places + * @return x rounded. + */ + public static double round(double x, int n) { + + if (Double.isNaN(x) || Double.isInfinite(x) || x == 0.0) { + // nans, infinities and zeros round to themselves + return x; + + } else { + + // (Slightly less than) n*log2(10). + float nlog2_10 = 3.3219f * n; + + // x = a * 2^b and a<2. + int b = Math.getExponent(x); + + if (nlog2_10 > 52 - b) { + // When n*log2(10) > nmax, the lsb of abs(x) is >1, so x rounds to itself. + return x; + } else if (nlog2_10 < -(b + 2)) { + // When n*log2(10) < -(b+2), abs(x)<0.5*10^n so x rounds to (signed) zero. + return Math.copySign(0.0, x); + } else { + // We have to work it out properly. + BigDecimal xx = new BigDecimal(x); + BigDecimal rr = xx.setScale(n, RoundingMode.HALF_UP); + return rr.doubleValue(); + } + } + } } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Apr 24 00:04:14 2014 From: jython-checkins at python.org (jeff.allen) Date: Thu, 24 Apr 2014 00:04:14 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge?= Message-ID: <3gDbJt49Hhz7LkF@mail.python.org> http://hg.python.org/jython/rev/d7c875ef210a changeset: 7219:d7c875ef210a parent: 7218:3fef65b876ec parent: 7212:02fd15a0075b user: Jeff Allen date: Wed Apr 23 23:02:05 2014 +0100 summary: Merge files: Lib/encodings/idna.py | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-) diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py --- a/Lib/encodings/idna.py +++ b/Lib/encodings/idna.py @@ -1,7 +1,12 @@ import codecs import re -from com.ibm.icu.text import StringPrep, StringPrepParseException from java.net import IDN +try: + # import from jarjar-ed version if available + from org.python.icu.text import StringPrep, StringPrepParseException +except ImportError: + # dev version of Jython, so use extlibs + from com.ibm.icu.text import StringPrep, StringPrepParseException # IDNA section 3.1 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Apr 26 00:47:58 2014 From: jython-checkins at python.org (jeff.allen) Date: Sat, 26 Apr 2014 00:47:58 +0200 (CEST) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_regression_in_ntpath=2E?= =?utf-8?q?abspath_when_tested_on_Linux=2E?= Message-ID: <3gFrBQ5YnXz7Llh@mail.python.org> http://hg.python.org/jython/rev/61ef07321554 changeset: 7220:61ef07321554 user: Jeff Allen date: Fri Apr 25 22:50:35 2014 +0100 summary: Fix regression in ntpath.abspath when tested on Linux. Calling sys.getPath was only valid on Windows, because it depends on java.io.File. files: Lib/ntpath.py | 62 +++++++++++++++++++++++++++----------- 1 files changed, 44 insertions(+), 18 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -451,27 +451,53 @@ try: from nt import _getfullpathname -except ImportError: # not running on Windows - mock up something sensible +except ImportError: # no built-in nt module - maybe it's Jython ;) - def abspath(path): - """Return the absolute version of a path.""" - try: - if isinstance(path, unicode): - if path: - path = sys.getPath(path) + if os._name == 'nt' : + # on Windows so Java version of sys deals in NT paths + def abspath(path): + """Return the absolute version of a path.""" + try: + if isinstance(path, unicode): + # Result must be unicode + if path: + path = sys.getPath(path) + else: + # Empty path must return current working directory + path = os.getcwdu() else: - # Empty path must return current working directory - path = os.getcwdu() - else: - if path: - path = sys.getPath(path).encode('latin-1') + # Result must be bytes + if path: + path = sys.getPath(path).encode('latin-1') + else: + # Empty path must return current working directory + path = os.getcwd() + except EnvironmentError: + pass # Bad path - return unchanged. + return normpath(path) + + else: + # not running on Windows - mock up something sensible + def abspath(path): + """Return the absolute version of a path.""" + try: + if isinstance(path, unicode): + # Result must be unicode + if path: + path = join(os.getcwdu(), path) + else: + # Empty path must return current working directory + path = os.getcwdu() else: - # Empty path must return current working directory - path = os.getcwd() - except EnvironmentError: - pass # Bad path - return unchanged. - - return normpath(path) + # Result must be bytes + if path: + path = join(os.getcwd(), path) + else: + # Empty path must return current working directory + path = os.getcwd() + except EnvironmentError: + pass # Bad path - return unchanged. + return normpath(path) else: # use native Windows method on Windows def abspath(path): -- Repository URL: http://hg.python.org/jython