5 Mar
2014
5 Mar
'14

4:42 a.m.

Greetings,

The purpose of this idea is to expand a bit on the decimal default idea
which I submitted previously. In this idea I want to suggest the human
idea of a *python number.* The concept is very simple, yet may have far
reaching implications not only for future python(s) but also for the wider
adaptation of python in the greater academic and professional communities.

The idea of *python number* means that there are no types, no limits, no
constraints, and that all *python numbers *are dynamic. The upper level
syntax is also very simple; all* python numbers *are simply human.

My influence for this preference is rooted in the Rexx programming
language; attributed to Mike Cowlishaw, former IBM fellow. The Rexx
programming language is dynamic, and has no types. To put it more
succinctly for those of you who have not used Rexx, the only data type
is a string of characters (that's it). *Rexx numbers* are simply those
strings of characters that may be interpreted as a valid *Rexx number.*

http://books.google.com/books?id=cNiVqFmPs8AC&pg=PA100&lpg=PA100&...

The Python language might be changed to adopt the *python number*
concept for *all math processing*, unless explicitly modified. This goes
somewhat beyond using decimal floating point as a default numerical
type. It means using human numeric expressions that meet human
expectation for numeric processing by default.

Under the covers (whatever we mean by that) processing is handled
by decimal.Decimal, unless explicitly modified. What does this mean for
python users in general? Well, no more worrying about types at all... no
ints,
no floats, no rationals, no irrationals, no fractions, and certainly no
binaries.
In short, if its a human number, its a *python number.*

I am expecting that (just as in Rexx numbers) defining very clearly what
is a *python number* will be key for wide adaptation of the concept. But
there
should be no surprises for users, particularly average users. Anyone with
a middle school expectation of a numeric format should be able to use
*python numbers *without surprises. However, for advanced users the full
interface should be available (as is the context for Decimal) through coding
based on knowledge and experience, yet the default context for Decimal
should be based on average users in most environments and use cases.

It is my belief that Python should lead the way in the twenty-first century for advanced computation for academic, professional, business, and scientific communities. There is a 40 year momentum for embedded binary floats & doubles, also numeric types generally, but it is time to move forward. The technology is ready, and the need is great. Let's do it.

{shameless plug} pdeclib https://pypi.python.org/pypi/pdeclib

Thank you for your consideration. Good evening.

Mark H Harris marcus

5 Mar
5 Mar

7:57 a.m.

On Wed, Mar 5, 2014 at 2:42 PM, Mark H. Harris harrismh777@gmail.com wrote:

The idea of python number means that there are no types, no limits, no constraints, and that all python numbers are dynamic. The upper level syntax is also very simple; all python numbers are simply human.

My influence for this preference is rooted in the Rexx programming language; attributed to Mike Cowlishaw, former IBM fellow. The Rexx programming language is dynamic, and has no types. To put it more succinctly for those of you who have not used Rexx, the only data type is a string of characters (that's it). Rexx numbers are simply those strings of characters that may be interpreted as a valid Rexx number.

I've used REXX extensively. (It's the native scripting language of OS/2, which I first met in the early 1990s and am still using - albeit usually in a VM under Linux these days - as it's still a good thing, just a little left-behind.) There are two huge downsides to that kind of proposal.

1) Performance. Huge huge performance hit. I can take Pike to OS/2, an unoptimized build on an unsupported platform, and absolutely cream a REXX program at any sort of computational work. By specifically working with integers rather than "numbers", I can run code blazingly fast.

2) Accuracy. With REXX, all arithmetic is governed by a single setting of NUMERIC DIGITS, which specifies how many digits of accuracy you want to preserve. (Python can improve granularity with separate contexts, but the same issue will still apply - contexts just let you use different NUMERIC DIGITS settings simultaneously in one program.) Set it too high and performance suffers because the system has to calculate more than you actually care about. Set it too low and accuracy suffers. Even when you're working with integers, going past DIGITS means it goes exponential.

3) Not so huge a downside, but also worth considering: REXX doesn't have complex number support at all. You have to simulate it with two variables. There's no way for the "Python number" system to intrinsically handle complexes.

So really, what you're looking at is unifying int/float/Decimal into a single data type, which is basically Decimal. I'd say merging int into that is a bad idea, but you can get most of what you want to achieve simply by encouraging the use of Decimal everywhere.

Maybe in some distant future version, the Python literal 1.234 will represent a Decimal rather than a float. (And then there'd be other consequences, like what you get when you divide one integer by another.) But until then, all you can really do is encourage people to explicitly use Decimal.

ChrisA

8:34 a.m.

On Wednesday, March 5, 2014 12:57:10 AM UTC-6, Chris Angelico wrote:

1) Performance. Huge huge performance hit. I can take Pike to OS/2, an unoptimized build on an unsupported platform, and absolutely cream a REXX program at any sort of computational work. By specifically working with integers rather than "numbers", I can run code blazingly fast.

2) Accuracy. With REXX, all arithmetic is governed by a single setting of NUMERIC DIGITS, which specifies how many digits of accuracy you want to preserve. (Python can improve granularity with separate contexts, but the same issue will still apply - contexts just let you use different NUMERIC DIGITS settings simultaneously in one program.)

hi Chris, good to hear from another Rexx programmer! Back in the day (during my 25 year tenure with IBM, I wrote thousands of scripts in Rexx for VM370 systems, and OS/2. I too still have OS/2 running. whoohoo!

Thanks for your comments. I would normally have agreed with you on this but not in this case. I do not think you are wrong, I just think you may have underestimated the power of decimal.Decimal /its phenomenal--really--!

Don't take the Rexx reference too much to heart, I just wanted folks to know where I got my influence on this thinking. Rexx was too slow... so was Decimal until 3.3, and then, boy has it taken off so-to-speak!

Implementation details are way off in the future of course, but I am thinking AI processing on this idea. The Python system will sense and optimize the decimal contexts (local and otherwise) so that things are balanced between speed and accuracy. And I would note, that speed is not as important for most python related aspects as user clarity and modern policy and account- ability. But, I'm not the least concerned for performance being a problem, because I have thoroughly tested decimal.Decimal and it flat screams, unlike our old friend Rexx.

Thanks again for your feedback on the idea. Please give it some more thought, and let me know what you think of AI for load/speed/accuracy balancing.

PS Have you considered the way things might have been if Palmisano had played ball with Bill Gates and Steve Ballmer back in the day... OS/2 would rule, gnu/linux might never have been invented period, and you and I might not be having this conversation today! ha! Go Big Blue.

marcus

9:10 a.m.

My previous reply got google-grouped, so let me try again.

From: Mark H. Harris harrismh777@gmail.com Sent: Tuesday, March 4, 2014 7:42 PM

The Python language might be changed to adopt the python number

concept for all math processing, unless explicitly modified. This goes somewhat beyond using decimal floating point as a default numerical type. It means using human numeric expressions that meet human expectation for numeric processing by default.

Different humans have different expectations in different situations. Trying to pick a single type that can handle all such expectations is an impossible dream.

Under the covers (whatever we mean by that) processing is handled by decimal.Decimal, unless explicitly modified. What does this mean for python users in general? Well, no more worrying about types at all... no ints, no floats, no rationals, no irrationals, no fractions, and certainly no binaries. In short, if its a human number, its a python number.

This is not really a meaningful concept. The types you dislike are not programming concepts that get in the way of human mathematics, they are human mathematical concepts. The integers, the rationals, and the reals all behave differently. And they're not the only types of numbers—Python handles complex numbers natively, and it's very easy to extend it with, say, quaternions or even arbitrary matrices that act like numbers.

The types that _are_ programming concepts—decimal and binary floats—are necessary, because you simply can't store real numbers in finite storage. And the very fact that they are inexact approximations means you can't ignore the types. For some uses, IEEE binary floats are best; for others, decimal floats are best; for others, fractions are best; for others, you even want to handle symbolic numbers like pi/2 exactly.

I am expecting that (just as in Rexx numbers) defining very clearly what

is a python number will be key for wide adaptation of the concept. But there should be no surprises for users, particularly average users. Anyone with a middle school expectation of a numeric format should be able to use python numbers without surprises.

Anyone with a middle school expectation will expect 1/3 to be a fraction—or, at least, something they can multiply by 3 to get exactly 1. Using an inexact decimal float instead of an inexact binary float is no improvement at all. Sure, it's an improvement in some _other_ cases, like 0.123, but if you want to deal with 1/3, today you can do so explicitly by using, say, Fraction(1, 3), while in your world it will no longer be possible.

And if you take the obvious way around that, you run into the same problem with 2 ** 0.5. Normal humans who write that would expect to be able to square it and get back 2, not an approximation to 2.

However, for advanced users the full interface should be available (as is the context for Decimal) through coding based on knowledge and experience, yet the default context for Decimal should be based on average users in most environments and use cases.

Is there a problem with the current default context?

I think just using Decimal as it is in Python today gets you everything you're asking for. Sure, you might want a nicer way to type them and repr them, which goes back to the previous thread, but why do you need to get rid of all of the other types as well?

10:36 a.m.

On Wed, Mar 5, 2014 at 6:34 PM, Mark H. Harris harrismh777@gmail.com wrote:

hi Chris, good to hear from another Rexx programmer! Back in the day (during my 25 year tenure with IBM, I wrote thousands of scripts in Rexx for VM370 systems, and OS/2. I too still have OS/2 running. whoohoo!

Don't take the Rexx reference too much to heart, I just wanted folks to know where I got my influence on this thinking. Rexx was too slow... so was Decimal until 3.3, and then, boy has it taken off so-to-speak!

Yes, that's true. But I've just done some performance testing. REXX on our OS/2 VM accounting server "Stanley" calculates 10000! in 14 seconds; Pike on the same hardware calculates 100000! in 4 seconds. (I didn't bother calculating 100000! in REXX, didn't feel like waiting.) In each case it was by this naive algorithm:

REXX: n=1; do i=1 to 10000; n=n*i; end
Pike: int n=1; for (int i=1;i<=100000;++i) n*=i;

Those were running on the same hardware, and going to a target an order of magnitude higher took a fraction of the time. That's what integer performance is like. (BTW, I set 'numeric digits' to something just a bit more than the target. REXX was pretty fast if I left digits low, and I could then look at the exponent to see what digits setting I needed. In Pike's case, the int type supports arbitrary precision anyway, so the net result is a fair comparison; I could write the digits out to a file and get the exact same result.)

From here on, I've switched computers to Sikorsky, who has more languages and more power than Stanley has. Timings here aren't technically comparable to the above ones, but they seem similar. I guess VirtualBox is doing a decent job of staying out of the way. Pike on Sikorsky:

gauge {int n=1; for (int i=1;i<=100000;++i) n*=i;}; (3) Result: 3.394805453

Okay. Now, Python.

Python 3.4.0rc1+ (default:a124b981a7a3, Mar 3 2014, 01:30:30) [GCC 4.7.2] on linux

def fac(top): t=time.time() n=1 for i in range(1,top+1): n*=i return time.time()-t fac(100000) 7.814447641372681

Roughly double Pike's time. (I suspect this may be largely because Pike has an optimization for machine-word integers. The difference is far starker if the algorithm used is less naive.) Now watch the result of a switch of data type.

def fac_d_maxprec(top): from decimal import Decimal, getcontext, MAX_PREC getcontext().prec=MAX_PREC t=time.time() n=Decimal("1") for i in range(1,top+1): n*=i return time.time()-t

(Note that the multiplication is still with ints. Doing all the multiplication with Decimals would mean a whole lot more calling of the constructor, which wouldn't be a fair comparison. If all of Python used Decimal, then range() would be returning Decimal.)

fac_d_maxprec(100000) 20.71300506591797

I also wrote an alternative version of the above which pre-calculates at low precision, then sets the precision to just enough, and recalculates. The timings were statistically identical to the above.

So there you are: A three to one difference. That's pretty amazing,
actually - the fact that it's not *far far* worse is a tribute to the
folks who gave us this highly optimized decimal.Decimal
implementation! But it's still dramatically slower than integer
calculations, and as you can see from the Pike version, the integer
figures can be cut a long way too.

I'm not sure that I want to give that up.

But, I'm not the least concerned for performance being a problem, because I have thoroughly tested decimal.Decimal and it flat screams, unlike our old friend Rexx.

Yes, that's true (although frankly, when I first met REXX on OS/2, it screamed compared to doing the job myself in C - sure, I could do integer calculations quicker in C, but if I wanted to go above the size of a long - 1<<32 - that was pretty hard), but the screaming isn't so impressive compared to a modern integer engine.

PS Have you considered the way things might have been if Palmisano had played ball with Bill Gates and Steve Ballmer back in the day... OS/2 would rule, gnu/linux might never have been invented period, and you and I might not be having this conversation today! ha! Go Big Blue.

Hmm, I don't think so. Linux had a rather different purpose, back then. But yeah, OS/2 was brilliant tech built in a hostile environment... I'd like to see some of its best parts lifted onto a Linux kernel, though (like the WorkPlace Shell - should be possible to run that on top of X11). Alas, most of OS/2 now is in the "was great in the 90s, but other things do the same job now" basket, and a closed-source system that boasts little above a GNU/Linux stack isn't really going to go anywhere much.

Still, it's hanging around. It's the best way I have for running 16-bit Windows programs, believe it or not. (And yes, I did try running Pastel Accounting under Wine. Win-OS/2 still beats Wine hands-down, probably because Wine developers don't see any reason to bother with supporting anything so ancient.)

ChrisA

12:29 p.m.

On 5 March 2014 08:10, Andrew Barnert abarnert@yahoo.com wrote:

From: Mark H. Harris harrismh777@gmail.com >

I am expecting that (just as in Rexx numbers) defining very clearly what is a python number will be key for wide adaptation of the concept. But there should be no surprises for users, particularly average users. Anyone with a middle school expectation of a numeric format should be able to use python numbers without surprises.

Anyone with a middle school expectation will expect 1/3 to be a fraction--or, at least, something they can multiply by 3 to get exactly 1. Using an inexact decimal float instead of an inexact binary float is no improvement at all.

I actually think that it is an improvement. Most people are surprised by the fact that just writing x = 0.1 causes a rounding error. Python only has dedicated syntax for specifying binary floats in terms of decimal digits meaning that there is no syntax for exactly specifying non integer numbers. I would say that that is clearly a sub-optimal situation.

I don't agree with Mark's proposal in this thread but I would like to have decimal literals e.g. 1.23d, and I would also use Fraction literals if available e.g. 1/3F.

Oscar

1:04 p.m.

On Wed, Mar 05, 2014 at 11:29:10AM +0000, Oscar Benjamin wrote:

I don't agree with Mark's proposal in this thread but I would like to have decimal literals e.g. 1.23d,

+1 on that. Although that will depend on how big a slowdown it causes to Python's startup time.

and I would also use Fraction literals if available e.g. 1/3F.

Out of curiosity, why F rather than f?

(By the way, I think it is somewhat amusing that Python not only has a
built-in complex type, but also *syntax* for creating complex numbers,
but no built-in support for exact rationals.)

-- Steven

1:30 p.m.

On 5 March 2014 12:04, Steven D'Aprano steve@pearwood.info wrote:

On Wed, Mar 05, 2014 at 11:29:10AM +0000, Oscar Benjamin wrote:

I don't agree with Mark's proposal in this thread but I would like to have decimal literals e.g. 1.23d,

+1 on that. Although that will depend on how big a slowdown it causes to Python's startup time.

If it is a significant slowdown then it can be a delayed import that only occurs when a decimal literal is first encountered. Applications that don't need decimal won't be slowed down. Applications that do would have had to import it anyway.

and I would also use Fraction literals if available e.g. 1/3F.

Out of curiosity, why F rather than f?

In C and some other languages the f suffix indicates a numeric literal that has type "float" e.g. "6.0f". You can use upper case there as well but the convention is lower case and to include the .0 when it's an integer. I just though that 3F looks sufficiently distinct from the way it's typically done in C.

Oscar

1:42 p.m.

On Wed, Mar 5, 2014 at 11:30 PM, Oscar Benjamin

oscar.j.benjamin@gmail.com wrote:

I just though that 3F looks sufficiently distinct from the way it's typically done in C.

3F would be about -16C wouldn't it?

(Not painting any bike sheds when it's that cold, thanks!)

I'm not sure I like the idea of tagging the end of the expression. Currently, the nearest Python has to that is the complex notation:

1+2j (1+2j)

And that works just fine when considered to be addition:

a=1 b=2j a+b (1+2j)

So really, what Python has is a notation for a j-suffixed float, meaning a complex number with no real part. You can't do that with Fraction:

a=2 b=3F a/b

What I'd prefer would be some modification of the division operator. We currently have two ways to divide an integer by an integer:

1234/10 123.4 1234//10 123

Adding a third operator, defined only between two integers, would be cleaner than tagging one of the integer literals. Exactly what that operator would be is hard to say, but I'm sure there's a combination that would look right and not be ambiguous.

1234-/-10 Fraction(617, 5)

I can't think of anything good, though.

ChrisA

1:56 p.m.

On 5 March 2014 12:42, Chris Angelico rosuav@gmail.com wrote:

On Wed, Mar 5, 2014 at 11:30 PM, Oscar Benjamin

oscar.j.benjamin@gmail.com wrote:

I just though that 3F looks sufficiently distinct from the way it's typically done in C.

3F would be about -16C wouldn't it?

(Not painting any bike sheds when it's that cold, thanks!)

I'm not sure I like the idea of tagging the end of the expression. Currently, the nearest Python has to that is the complex notation:

1+2j (1+2j)

And that works just fine when considered to be addition:

a=1 b=2j a+b (1+2j)

So really, what Python has is a notation for a j-suffixed float, meaning a complex number with no real part. You can't do that with Fraction:

a=2 b=3F a/b

From a syntactic perspective Python doesn't have syntax for general complex literals. There is only syntax for creating imaginary literals and it is trivial to create a complex number by adding a real and an imaginary one.

3F could create a Fraction with the integer value 3 so that a/b gives a rational number:

from fractions import Fraction as F a = 2 b = F(3) a/b Fraction(2, 3)

I don't understand why you say that can't be done.

Oscar

2:21 p.m.

On Wed, Mar 05, 2014 at 12:10:18AM -0800, Andrew Barnert wrote:

Anyone with a middle school expectation will expect 1/3 to be a fraction—or, at least, something they can multiply by 3 to get exactly 1.

Not a very good example -- that happens to work for Python floats (which are C doubles under the hood):

py> (1/3)*3 == 1 True py> (1/3 + 1/3 + 1/3) == 1 True

But it *does not work* with Decimal, at least not with the default
precision:

py> from decimal import Decimal py> (Decimal(1)/3)*3 Decimal('0.9999999999999999999999999999')

Decimal is not a panacea! It does not eliminate floating point issues. Between 1 and 100, there are 32 Decimal numbers that fail the test that (1/n)*n == 1, and only two floats.

Between 1 and 100, there are only four floats where 1/(1/n) does not equal n: 49 93 98 and 99. In comparison, there are 46 such failing Decimals, including 6 7 and 9.

-- Steven

2:23 p.m.

On Wed, Mar 05, 2014 at 11:42:14PM +1100, Chris Angelico wrote:

I'm not sure I like the idea of tagging the end of the expression. Currently, the nearest Python has to that is the complex notation:

1+2j (1+2j)

And that works just fine when considered to be addition:

a=1 b=2j a+b (1+2j)

So really, what Python has is a notation for a j-suffixed float, meaning a complex number with no real part. You can't do that with Fraction:

a=2 b=3F a/b

Why not? If 3d is a Decimal with the value of 3, why couldn't 3F be a Fraction with the value of 3?

-- Steven

2:39 p.m.

Steven D'Aprano:

I don't agree with Mark's proposal in this thread but I would like to have decimal literals e.g. 1.23d,

+1 on that. Although that will depend on how big a slowdown it causes to Python's startup time.

Startup time should not be a problem once http://bugs.python.org/issue19232 is dealt with.

Stefan Krah

2:56 p.m.

On Tue, Mar 04, 2014 at 07:42:28PM -0800, Mark H. Harris wrote:

The idea of *python number* means that there
are no types, no limits, no
constraints, and that all *python numbers *are dynamic. The upper level
syntax is also very simple; all* python numbers *are simply human.

What makes this a "python number"? In what way are they "dynamic"?

My influence for this preference is rooted in the
Rexx programming
language; attributed to Mike Cowlishaw, former IBM fellow. The Rexx
programming language is dynamic, and has no types. To put it more
succinctly for those of you who have not used Rexx, the only data type
is a string of characters (that's it). *Rexx numbers* are simply those
strings of characters that may be interpreted as a valid *Rexx number.*

I haven't used Rexx, but I have used Hypertalk, which worked the same way. If you don't care about performance, it can work quite well.

The Python language might be changed to adopt the
*python number*
concept for *all math processing*, unless explicitly modified.

Well, there's a bit of a problem here. Numbers in Python are not just
used for maths processing. They're also used for indexing into lists, as
keys in dicts, for bitwise operations, for compatibility with external
libraries that have to interface with other languages, as flags, etc.
For some of these purposes, we *really do* want to distinguish between
ints and floats that happen to have the same value:

mylist[3] # allowed mylist[3.0] # not allowed

Now, you might argue that this distinction is unnecessary, but it runs quite deep in Python. You'd need to change that philosophy for this idea to work.

This goes somewhat beyond using decimal floating point as a default numerical type. It means using human numeric expressions that meet human expectation for numeric processing by default.

I don't understand what that means, unless it means that you want Python to somehow, magically, make all the unintuitive issues with floating point to disappear. Good luck with that one.

If you want that, Decimal is not the answer. It would have to be a Rational type, like Fraction, although even that doesn't support surds. Fractions have their own problems too. Compare the status quo:

py> 3**0.5 1.7320508075688772

with a hypothetical version that treats all numbers as exact fractions:

py> 3**0.5 Fraction(3900231685776981, 2251799813685248)

Which do you think the average person using Python as a calculator would prefer to see?

And another issue: before he invented Python, Guido spent a lot of time working with ABC, which used Fractions as the native number type. The experience soured him on the idea for nearly two decades. Although Guido has softened his stance enough to allow the fractions module into the standard library, I doubt he would allow Fractions to become the underlying implementation of numbers in Python.

The problem is that fractions can be unbounded in memory, and some simple operations become extremely inefficient. For example, without converting to float, which is bigger?

Fraction(296, 701) Fraction(355, 594)

For many purposes, the fact that floats (whether binary or decimal) have finite precision and hence introduce rounding error is actually a good thing. Compare:

py> 1e-300 + 1e300 1e+300

versus fractions:

py> from fractions import Fraction as F
py> F(10)**-300 + F(10)**300
Fraction(100000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000001, 100000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000)

There are *very few* applications where such precision is needed or
wanted. The performance hit in calculating such excessively precise
numbers when the user doesn't need it will be painful.

The same applies to Decimal, although to a lesser extent since Decimals do have a finite precision. Unlike fractions, they cannot grow without limit:

py> Decimal("1e-300") + Decimal("1e300") Decimal('1.000000000000000000000000000E+300')

-- Steven

4:23 p.m.

On Wed, Mar 5, 2014 at 11:56 PM, Oscar Benjamin

oscar.j.benjamin@gmail.com wrote:

3F could create a Fraction with the integer value 3 so that a/b gives a rational number:

from fractions import Fraction as F a = 2 b = F(3) a/b Fraction(2, 3)

I don't understand why you say that can't be done.

(Also Steven who said the same thing.)

Uhh... brown-paper-bag moment. When I wrote up that post, I somehow blanked out the obvious fact that Fraction can happily represent an integer. Whoops...

As Julia Jellicoe said, my objection falls to the ground. Very well!

ChrisA

4:38 p.m.

On 03/05/2014 06:04 AM, Steven D'Aprano wrote:

(By the way, I think it is somewhat amusing that
Python not only has a
built-in complex type, but also*syntax* for creating complex numbers,
but no built-in support for exact rationals.)

That is interesting.

I think Mark is correct in unifying numbers. And also adding in decimal features. The way it should actually be done is another thing. But having an up to date package that can be used is a very good start. (Thanks Mark!)

Mark describes an AI approach,, which I think he means having a internal representation that may change as needed depending on how a number can best be stored and calculated while still keeping it's accuracy.

Weather or not that approach is called Decimals is another thing. It might be called "Unified Numbers".. or just Numbers.

The point is for the internal representation to be an implementation detail the user doesn't need to worry about. And have Decimal features available by default.

If things are decided by use case, then I can't even think of a good enough argument against having decimal features available by default. The financial use cases are that overwhelming.

-Ron

9:32 p.m.

From: Oscar Benjamin oscar.j.benjamin@gmail.com

Sent: Wednesday, March 5, 2014 3:29 AM

On 5 March 2014 08:10, Andrew Barnert abarnert@yahoo.com wrote:

From: Mark H. Harris harrismh777@gmail.com

I am expecting that (just as in Rexx numbers) defining very clearly what is a python number will be key for wide adaptation of the concept. But there should be no surprises for users, particularly average users. Anyone with a middle school expectation of a numeric format should be able to use python numbers without surprises.

Anyone with a middle school expectation will expect 1/3 to be a fraction--or, at least, something they can multiply by 3 to get exactly 1. Using an inexact decimal float instead of an inexact binary float is no improvement at all.

I actually think that it is an improvement. Most people are surprised by the fact that just writing x = 0.1 causes a rounding error. Python only has dedicated syntax for specifying binary floats in terms of decimal digits meaning that there is no syntax for exactly specifying non integer numbers. I would say that that is clearly a sub-optimal situation.

I don't agree with Mark's proposal in this thread but I would like to have decimal literals e.g. 1.23d, and I would also use Fraction literals if available e.g. 1/3F.

I agree with you completely on that. That's kind of my point—your suggestion is almost the _opposite_ of the proposal in this thread. You want to make it easier for people to use the appropriate type for each use case; he wants to eliminate that choice entirely so you never have to make it. The latter would be nice if it were doable, but it's not even possible in principle. So the former is the best choice.

10:13 p.m.

On 03/05/2014 02:32 PM, Andrew Barnert wrote:

I don't agree with Mark's proposal in this thread but I would like to have decimal literals e.g. 1.23d, and I would also use Fraction literals if available e.g. 1/3F.

I agree with you completely on that. That's kind of my point—your suggestion is almost the_opposite_ of the proposal in this thread. You want to make it easier for people to use the appropriate type for each use case; he wants to eliminate that choice entirely so you never have to make it. The latter would be nice if it were doable, but it's not even possible in principle. So the former is the best choice.

It's also a matter of how far in the future you are looking. If he waited until later to propose something like this, it most likely wouldn't get in. I'm not sure he's expecting a firm answer now, but probably is hoping for a "hey lets look into this more and see if it's really possible" kind of maybe. For that, it's good to start early.

On the near term, adding literal syntax's as you describe here would get all the pieces into python. Then an import from future could enable what Mark is thinking. He really needs to flesh out the details first before we can make any objective opinions about how it would actually work.

The way I see it, with a unified number type, we will still need context like api's to get the more specialised behaviours. The difference may be a decorator on a function that specifies some subset of number types to use rather than notating each literal and casting each value.

```
@numbers(int, decimal)
def acounting_foo(...):
...
# using ints for integers
# using decimal for reals
...
```

That just a guess... maybe Mark can give some examples how what he's proposing would work. ?

Cheers, Ron

11:36 p.m.

Steven D'Aprano wrote:

(By the way, I think it is somewhat amusing that
Python not only has a
built-in complex type, but also *syntax* for creating complex numbers,
but no built-in support for exact rationals.)

I gather that Guido's experiences with ABC have led him to believe such a feature would be an attractive nuisance. Rationals have some nasty performance traps if you use them without being fully aware of what you're doing.

-- Greg

11:49 p.m.

Oscar Benjamin wrote:

In C and some other languages the f suffix indicates a numeric literal that has type "float" e.g. "6.0f". You can use upper case there as well but the convention is lower case and to include the .0 when it's an integer. I just though that 3F looks sufficiently distinct from the way it's typically done in C.

I'm not sure it's different enough; I've been looking at Java code recently that uses uppercase F.

An alternative would be to use 'r' or 'R' for 'rational', which would eliminate any chance of confusion.

-- Greg

6 Mar
6 Mar

12:24 a.m.

On 5 March 2014 22:36, Greg Ewing greg.ewing@canterbury.ac.nz wrote:

Steven D'Aprano wrote: >

*syntax* for creating complex numbers, but
no built-in support for exact rationals.)

I gather that Guido's experiences with ABC have led him to believe such a feature would be an attractive nuisance. Rationals have some nasty performance traps if you use them without being fully aware of what you're doing.

I've read statement's to that effect but they were specifically about
having rational as the *default* mode for integer division which I am
not suggesting.

It's already possible to opt-in to using Fractions so that rationals are used for division. Currently you have to import a module and write F(1, 7) or F('1/7') everywhere in your code. If it were possible to write 1/7F or 1/7r or whatever then I've personally written code where I would have used that so that it would be easier to read, would look more natural with syntax highlighting etc.

Oscar

1:30 a.m.

On Wednesday, March 5, 2014 2:10:18 AM UTC-6, Andrew Barnert wrote:

The types you dislike are not programming concepts that get in the way of

human mathematics, they are human mathematical concepts. The integers, the rationals, and the reals all behave differently. And they're not the only types of numbers—Python handles complex numbers natively, and it's very easy to extend it with, say, quaternions or even arbitrary matrices that act like numbers.

hi Andrew, your response reminds me of that scene from 'Star Trek: First
Contact,'

when Data was getting a little lippy with her eminence The Borg (the one
who is many)

(he was trying to explain to her why she doesn't exist, since her ship was
destroyed
just after coming through the chronometric particle vortex--time travel--)
and she
responded, "You think so three dimensionally... "

When asked about the Borg hierarchy she stated simply, "You imply disparity where none exists..."

Now, that was one hot cybernetic babe. My point is philosophical, really. I do not dislike numerical types. I simply do not believe they are necessary. In fact, Rexx (in a simple way) and other languages too for that matter, have demonstrated that types are superficial paradigms in symbolic thought that can quite readily be eliminated for most AI applications given enough memory, and enough time. Fortunately, memory is becoming more plentiful (less expensive) and time is shrinking away through processor speed, pipe-lining, and software engineering like decimal.Decimal.

Keep in mind that I am NOT proposing the elimination of numbers, I am
proposing the
abstraction of *python numbers (yes, all of them...) *in such a way that
programmers and
specialists, and accountants and grade school teachers may use Python
without having
to be computer scientists (all of the work happens under the covers,
written by someone
like me, or you) and everything at the top is simple and human readable;
for them.

Yes, its work. And, its doable. A *number *should be no more convoluted
in a modern age
of symbol set processing than any other abstract idea (just because of
arbitrary limits
and abstraction paradigms). Unifying *python numbers *requires a high
order paradigm
shift for sure; but the concept is absolutely doable.

kind regards,

1:40 a.m.

On Wednesday, March 5, 2014 3:13:29 PM UTC-6, Ron Adam wrote: >

It's also a matter of how far in the future you are looking. If he waited

until later to propose something like this, it most likely wouldn't get in.

I'm not sure he's expecting a firm answer now, but probably is hoping for

a "hey lets look into this more and see if it's really possible" kind of maybe. For that, it's good to start early.

hi Ron, yes, correct. Something like this would require huge effort, determined strategy, and possibly years of planning. Its not going to happen over-night, but in stages could be brought to fruition given some thought and a lot of dialogue.

kind regards,

2:06 a.m.

On Wednesday, March 5, 2014 7:56:46 AM UTC-6, Steven D'Aprano wrote: >

On Tue, Mar 04, 2014 at 07:42:28PM -0800, Mark H. Harris wrote:

*python number* means that
there are no types, no limits,
no
constraints, and that all *python numbers *are dynamic. The upper level
syntax is also very simple; all* python numbers *are simply human.

What makes this a "python number"? In what way are they "dynamic"?

At this point in the discussion please try not to think "implementation,"
rather think
conceptually in an open framework where anything is possible. Dynamic in
terms
of *python numbers *is not that far off from the concept of dynamic name
binding used
everywhere else in python.

In terms of unifying numbers as *python numbers *which needs to be
defined at some
point, the idea of dynamic binding means first that numeric symbol sets
have no type,
and are not 'bound' until run-time, if at all. Consider:

What is this? ===> PI / 2

Is it two numbers with a divisor,? or a Decimal and an int with a
divisor,? or a *python number ?*

Or, is it a string of characters that are readily recognizable to humans that means 'the top of the unit circle," or half_of_PI whatever I mean by that, or '1.57079632679489661923132169163975144209858469968755291048747229615390820314'

Dynamic means the symbols sets are bound (name-wise, abstraction-wise) only at run-time, and with some 'smarts' as it were (AI) so that the right things happen under the covers (not magic) because the machine has been taught to think, as it were, about numbers and symbols the way normal humans do (in the kitchen, at high school, at the university, in science and business).

Another way to think of this Steven is to think of 'pretty print' on the
TI 89 Platinum edition
graphical programming calculator (my personal favorite). Anyone who plays
with it the first time
is frustrated because *numbers *are symbolized instead of printed out in
strings of numerals. So
if I enter {1} [/] {3} and press enter, I get 1/3 <====

Yes, I can press evaluate and get .33333333333333333 but 1/3 is just as good a number as any other number (and what type is it???) PI / 2 is another example. That's a great number and I don't really need to see a numeric list of digits to use it, do I... and what is its type??? who cares.

Another very simple (and not quite good enough) example is the pdeclib file I just created over the past couple of days. I have a need within the module to use PI or half_PI for some method or function (but I don't need the user to know that, and I don't want to calculate it over and over, and I want it within accuracy for the context on demand)... so what? I created __gpi__ global PI. It is reset to Decimal(0) if the dscale(n) precision context changes. Then, and function that requires PI of half_PI can check first to see if it is cached as __gpi__ and if so pull it back, or if not calculate it. If the context does not change than I half some whopping value of PI for the duration of the context (but, and this is the big but, if I don't need it --- then it never gets bound).

Binding for *python numbers * must be dynamic, typeless, limitless, and no
constraints. If I enter a
formula of symbols into python (whatever I mean by that) python just
handles it dynamically just
like the TI 89--- if I want explicit control of the processing, I am free
to ask for that too, just like
the TI 89.

By the by, complex numbers are no different what-so-ever, nor fractions
either, nor any other *number.*
The system needs to be able to parse symbol sets intelligently (Alonzo
Church, Alan Turning, and John
McCarthy dreamed of this, but had no way to accomplish it in their day) and
then dynamically bind
the naming / representation to the abstraction (or implementation) that
makes sense for that symbol
set in 'that' context, for 'that' problem. This is not simply business as
usual... it requires AI, it requires
a severe paradigm shift of a very high order.

Kind regards, marcus

3:21 a.m.

> >

Then, and function that requires PI of half_PI can check first to see if it is cached as __gpi__ and if so pull it back, or if not calculate it. If the context does not change than I half some whopping value of PI for the duration of the context (but, and this is the big but, if I don't need it --- then it never gets bound).

um, sorry, I wixed my mords... please forgive,

"Then, and only then, a function that requires PI or half_PI

will first check to see if __gpi__ has been cached and if so pull it
back, or if not calculate it (once). If the context
does not change then the function can take half of some whopping value of
PI, for the duration of the context, and
use that, but if not needed for the context will never bind it !

marcus

4:47 a.m.

I wonder if we need yet another list, python-speculative-ideas? Or python-waxing-philosophically? I've got a feeling were straying quite far from the topic of the design of even Python 4, let alone what could possibly happen in Python 3.

-- --Guido van Rossum (python.org/~guido)

5:05 a.m.

On Wednesday, March 5, 2014 9:47:24 PM UTC-6, Guido van Rossum wrote: >

I wonder if we need yet another list, python-speculative-ideas? Or python-waxing-philosophically?

hi Guido, no probably not. I just got to thinking again about *number
*again
as
I was thinking about decimal floating point as default. Its the
mathematician in me; go
for the general case, and that would be a type-less unified *number *system.

For the near case design issues the following realist ideas might be considered in order:

1) decimal literal like 1.23d

2) default decimal floating point with a binary type 1.23b

3) type-less unified *number *yes, probably way out there into the future

kind regards, marcus

6:56 a.m.

Do you actually have a degree in math, or do you just remember your high school algebra? The numbers in math usually are quite strictly typed: whole theories only apply to integers or even positive integers, other things to all reals but not to complex numbers. (And what about quaternions? Or various infinities? :-)

On Wed, Mar 5, 2014 at 8:05 PM, Mark H. Harris harrismh777@gmail.comwrote:

> >

On Wednesday, March 5, 2014 9:47:24 PM UTC-6, Guido van Rossum wrote: >

I wonder if we need yet another list, python-speculative-ideas? Or python-waxing-philosophically?

hi Guido, no probably not. I just got to thinking again about *number
*again
as
I was thinking about decimal floating point as default. Its the
mathematician in me; go
for the general case, and that would be a type-less unified *number *
system.

For the near case design issues the following realist ideas might be considered in order:

1) decimal literal like 1.23d

2) default decimal floating point with a binary type 1.23b

3) type-less unified *number *yes, probably way out there into the future

kind regards, marcus

-- --Guido van Rossum (python.org/~guido)

9:14 a.m.

On Wednesday, March 5, 2014 11:56:02 PM UTC-6, Guido van Rossum wrote: >

Do you actually have a degree in math, or do you just remember your high school algebra?

hi Guido, ouch. Its a story, glad you asked. My first trip to college
1974-1977

(UMKC) was to study EE; minor in mathematics. I completed my math course
work
(Calc 1-5, Diff Eq, Linear Algebra, Theory of Stats and Prob, &c) ... all
(A). Not
that it matters, because in 1977 IBM made me an offer in their CE
department at
Kansas City... so I joined IBM for the next 25 years and did not complete
my EE
degree... but, I did complete my mathematics training. Of course, prior to
UMKC
I attended a high school that offered calculus, math analysis, trig... and
intro to
linear algebra. So by the time I joined IBM I got the math twice. So,
yeah, I know
what I'm doing. I am an amateur mathematician today, computer scientist,
and
computer hobbyist. While at IBM I was a staff software engineer, Tampa,
Chicago,
Atlanta, and at the lab at Rochester MN (where I left IBM in 2002; yet
I still dwell there). Education is a life long commitment and I continue to
study math, comp sci, music
and philosophy. I just completed my course work for the MDiv degree at
Bethel Seminary.

Back in the day, I added the scientific and transcendental math functions
to the Rexx
library for the internal VM370 systems, because Rexx (like python) also had
no decimal
floating point math package. So, yeah, its one of the things I know how to
do, and its one
of those things that most people never think about; but I've noticed that
they appreciate
having it once the work is done. My pdeclib package on PyPI is in infancy
stage, probably
has bugs (yet nobody has complained yet) and pdeclib will have to mature
there for some time.
But that has really nothing to do with whether we continue to use IEEE 754
1985 floats & doubles
nor whether we discuss default decimal floating point arithmetic nor
whether we discuss a unified
*python number *sytem (sometime in the distant future) that would make |
allow common average
ordinary people to leverage mathematics in computer science without having
to understand the
underlying mechanics of implementation including but not limited to types.

We might agree to stick with the discussion, if you're are willing, and
stay away from *ad hominem*
attacks, nor credential bashing. A person either knows what they are doing,
or they don't. And if
they are willing to contribute, why knock it, or them?

The numbers in math usually are quite strictly typed: whole theories only apply to integers or even positive integers, other things to all reals but not to complex numbers.

Everyone keeps making the points noted above! Yes, I know... concur~
Guido, all data types in most computer high level languages
are all very strictly statically bound also. So what? You know this better
than anyone, because you have taken so much abuse from trolls about it over
the
years. We all know that the best way to handle name binding is dynamically.
And that
was a paradigm shift, was it not? Go take a look at Rexx. It has NO types.
None.
Not at the surface, anyway. Everything to the user of the language is a
string. This is
even true for most of "object" Rexx too. *Numbers *are just strings of
characters that
are parsed and interpreted by Rexx as valid *Rexx Numbers*. It works. There
is no
real point in arguing that its a bad idea, because it served the Rexx
community for
many years... even now, Rexx is not dead. Yes, under the covers (not magic)
a complex
number is going to be handled differently than a real number. Yeah, what
is your point?
They are handled differently in my TI 89 too... so what, I change the
context on the 89
and now I'm doing complex number math (not often, I might add). If I set
the context for
reals, then now I'm doing real math... it really is not that difficult.
Think about this for just
a minute. I have used complex math exactly three times in my life. I used
it in high school
to study the concept in math analysis. I used it in my EE classes...
electrical engineers
get a lot out of complex numbers. And I used it when I was interested in
the Mendlebrot
set many years ago when it was cool to plot the famous fractal on early cga
screens.
When was the last time you used complex numbers? When was the last time a
bank, or
a hospital, or Montgomery Wards used complex numbers? If someone needs
complex
numbers, Python could change the context "dynamically" and move through the
problem set.

Why should the user need to "manually" change the context if (AI) could
change it for them
on-the-fly? Just try to get a feel for the question, and stop with trying
to beat me up on
my credentials.

(And what about quaternions? Or various infinities? :-)

What about quaternions? If you extend the complex number system you change the context. That's not hard... You would not expect the context for "reals" processing to be the same for the context of processing three dimensional space with pairs of complex numbers, would you? Python does complex number processing now. But that is a very specialized category of use case that requires special context and (under the covers) typing relevant to complex number pairs. The context can change "dynamically" to suit the problem set, that's all I'm saying.

I am not saying in all of my dreaming here that typing is not important (down under). Python is called an object based language, yes? Not object oriented, right? But we all know that down under the covers (not magic) python uses Classes and instances of classes--objects. We don't pretend that what makes object based languages work is magic? do we? Unifying the number system on a computer does not have to be grounded in paradigms that serviced the industry (and academy) for the past 40 years; mostly because memory was expensive and processing was slow. I suggest that if memory had been cheap, back in the day, and processing had been very fast (as it is today) IEEE 754 1985 floats and doubles would never have been used. We would have used Decimal floating points right from the get-go. Initially, that really is all I'm asking for on the outset--- lets move to default decimal floating point arithmetic for real numbers processing.

In the future I am thinking about systems of languages that interact with
human speech; understanding
*spoken number* just like they will understand *symbol number.* There is
really no reason (other
than paradigm) that this would not be possible either.

Otherwise, Guido, we might all of us just continue to use C and code up our statically bound types by hand using nothing but int, long, float, and double.

Its time to innovate.

Kind regards, BDfL,

marcus

1:36 p.m.

Mark H. Harris harrismh777@... writes:

At this point in the discussion please try not to think "implementation," rather think conceptually in an open framework where anything is possible.

As I understand, you are suggesting some kind of number system with symbolic representations that can be converted to decimal if needed.

The topic is so vast that the only chance of it happening is for someone to provide an implementation on PyPI and see if people like it. There are packages for symbolic mathematics like sympy, perhaps that is a start.

Even if people like it, it would be nearly impossible to integrate it into the core language without breaking third-party packages on a very large scale. So it is unlikely that such a thing will make it into the (hypothetical) 4.0.

Regarding decimal literals:

Possible in 3.x, but it would require some investigation if people really want arbitrary precision arithmetic by default rather than e.g. IEEE Decimal64.

Regarding decimal floats by default:

Perhaps in 4.0, but IMO only with *full* backing by the large SciPy
and NumPy communities. My guess is that it's not going to happen, not
in the least because the literature for floating point algorithms
(with proofs!) has an overwhelming focus on binary floating point.

1:46 p.m.

Steven D'Aprano writes:

The problem is that fractions can be unbounded in memory,

Seminumerical Algorithms has a few problems on "floating slash", ie a representation of numbers where the total memory allocated to a fraction is fixed, but the amounts allocated to numerator and denominator are variable. I don't know if it has ever been tried in practice, though. And of course any reasonable fixed size would have very limited ability to express very large or very small magnitudes.

1:51 p.m.

On 6 March 2014 12:36, Stefan Krah stefan@bytereef.org wrote: >

Regarding decimal literals:

Possible in 3.x, but it would require some investigation if people really want arbitrary precision arithmetic by default rather than e.g. IEEE Decimal64.

Interesting. I wasn't aware of Decimal64.

I don't understand your question/point though. The Decimal module doesn't provide "arbitrary" precision arithmetic by default. It provides 28 digit precision arithmetic by default. It does however allow the creation of Decimal instances with effectively arbitrary precision from strings, integers etc.

So I would assume that the idea was that nothing would change about decimal arithmetic (by default or when importing the module). The difference would just be that you could write 1.12345e-23d which would be equivalent to Decimal('1.12345e-23').

In this way it would be possible with a literal to express any decimal value exactly (even if it exceeds the arithmetic precision). It would be possible to do e.g. "if x <= 0.00001d:" and know that the test was exact.

Regarding decimal floats by default:

Perhaps in 4.0, but IMO only with *full* backing by the large SciPy
and NumPy communities. My guess is that it's not going to happen, not
in the least because the literature for floating point algorithms
(with proofs!) has an overwhelming focus on binary floating point.

Yes, it's very hard to gauge the effect of something like this. Understanding where in a numeric code base 64-bit binary floating point is assumed would be harder than disentangling unicode, from bytes, from 8-bit encodings I expect.

Oscar

4:49 p.m.

Oscar Benjamin oscar.j.benjamin@... writes:

On 6 March 2014 12:36, Stefan Krah stefan@... wrote:

Regarding decimal literals:

Possible in 3.x, but it would require some investigation if people really want arbitrary precision arithmetic by default rather than e.g. IEEE Decimal64.

Interesting. I wasn't aware of Decimal64.

I don't understand your question/point though. The Decimal module doesn't provide "arbitrary" precision arithmetic by default. It provides 28 digit precision arithmetic by default. It does however allow the creation of Decimal instances with effectively arbitrary precision from strings, integers etc.

Well, yes. You can also emulate Decimal64 with the appropriate context parameters.

I meant "arbitrary precision facilities by setting context parameters", but probably many people want to keep those.

Due to the compact representation (bid64 or bid128), Decimal64 or Decimal128 would be interesting for doing arithmetic on huge matrices.

Stefan Krah

5:41 p.m.

On 6 March 2014 15:49, Stefan Krah stefan@bytereef.org wrote:

Oscar Benjamin oscar.j.benjamin@... writes:

On 6 March 2014 12:36, Stefan Krah stefan@... wrote:

Regarding decimal literals:

Interesting. I wasn't aware of Decimal64.

I don't understand your question/point though. The Decimal module doesn't provide "arbitrary" precision arithmetic by default. It provides 28 digit precision arithmetic by default. It does however allow the creation of Decimal instances with effectively arbitrary precision from strings, integers etc.

Well, yes. You can also emulate Decimal64 with the appropriate context parameters.

I meant "arbitrary precision facilities by setting context parameters", but probably many people want to keep those.

Due to the compact representation (bid64 or bid128), Decimal64 or Decimal128 would be interesting for doing arithmetic on huge matrices.

I'm still not totally clear here.

Do you mean that decimals created with a decimal literal e.g. 1.123d would be some other kind of decimal object that always used a special arithmetic context so that they behaved like a fixed width Decimal64 type? And then someone who wants to do decimal arithmetic with other contexts would still need to import decimal and do the context stuff themselves?

Oscar

6:48 p.m.

On Thu, Mar 6, 2014 at 12:14 AM, Mark H. Harris harrismh777@gmail.comwrote:

On Wednesday, March 5, 2014 11:56:02 PM UTC-6, Guido van Rossum wrote: >

Do you actually have a degree in math, or do you just remember your high school algebra?

hi Guido, ouch. Its a story, glad you asked. My first trip to college 1974-1977 [...]

I must have hit a nerve -- I wasn't requesting a transcript! :-) A simple "I dropped out of math in college to pursue a career in programming" would have been sufficient (my own story is pretty similar :-).

We might agree to stick with the discussion, if
you're are willing, and
stay away from *ad hominem*
attacks, nor credential bashing. A person either knows what they are
doing, or they don't. And if
they are willing to contribute, why knock it, or them?

I was asking because I am having a hard time getting your point, and it feels like you keep repeating it without clarifying it.

The numbers in math usually are quite strictly typed: whole theories only

apply to integers or even positive integers, other things to all reals but not to complex numbers.

Everyone keeps making the points noted above! Yes, I know... concur~ Guido, all data types in most computer high level languages are all very strictly statically bound also.

I'm not sure that that description applies to Python; it sure doesn't match
how I *think* about numbers in Python.

So what? You know this better than anyone, because you have taken so much abuse from trolls about it over the years.

Actually, I haven't -- I can smell a troll a mile away and just mute the conversation.

We all know that the best way to handle name binding is dynamically.

That's your opinion. There are pros and cons and both static and dynamic binding have their place.

And that was a paradigm shift, was it not?

I've never claimed such a hyperbole.

Go take a look at Rexx. It has NO types. None.
Not at the surface, anyway. Everything to the user of the language is a
string. This is
even true for most of "object" Rexx too. *Numbers *are just strings of
characters that
are parsed and interpreted by Rexx as valid *Rexx Numbers*.

Why do you italicize this?

It works. There is no real point in arguing that its a bad idea, because it served the Rexx community for many years... even now, Rexx is not dead. Yes, under the covers (not magic) a complex number is going to be handled differently than a real number. Yeah, what is your point? They are handled differently in my TI 89 too... so what, I change the context on the 89 and now I'm doing complex number math (not often, I might add).

By "context", here, do you mean some specific state in the computer, or are you referring to a different way of thinking about it (i.e. in your head)?

If I set the context for reals, then now I'm doing real math... it really is not that difficult.

So I suppose depending on how you set the context, the square root of -1 either returns a complex number, or raises an error? And comparisons are right out once your context is set for complex numbers? Or are they still allowed when the imaginary part is exactly zero? (What if it is nearly zero?)

Think about this for just a minute. I have used complex math exactly three times in my life. I used it in high school to study the concept in math analysis. I used it in my EE classes... electrical engineers get a lot out of complex numbers. And I used it when I was interested in the Mendlebrot set many years ago when it was cool to plot the famous fractal on early cga screens. When was the last time you used complex numbers? When was the last time a bank, or a hospital, or Montgomery Wards used complex numbers? If someone needs complex numbers, Python could change the context "dynamically" and move through the problem set. Why should the user need to "manually" change the context if (AI) could change it for them on-the-fly? Just try to get a feel for the question, and stop with trying to beat me up on my credentials.

I still don't understand the question. Can you be more precise instead of using more rhetoric? Is your point just that you don't want to have to deal with complex numbers? That's fine, and it's already (mostly) how Python works -- you must use a complex literal or the cmath module before any complex numbers appear (the exception is that x**y returns a complex number when x is negative and y is not a whole number -- but arguably that's the best answer).

(And what about quaternions? Or various infinities? :-) >

What about quaternions? If you extend the complex number system you change the context. That's not hard...

So, by context you really seem to be referring to some specific state of the program. Perhaps you are even talking about extending the existing notion of numeric context used by Python's Decimal type. But it's not very clear because "you change the context" is also a common term for changing the topic of a discussion.

You would not expect the context for "reals" processing to be the same for

the context of processing three dimensional space with pairs of complex numbers, would you?

Actually I'm not sure I *need* a context for this. In Python things like
this are usually just solved through a combination of operator overloading
and dynamic binding, so that e.g. x*y can be a number when x and y are
numbers, but it can mean a vector product of some sort if x and/or y are
vectors.

Python does complex number processing now. But that is a very specialized category of use case that requires special context and (under the covers) typing relevant to complex number pairs. The context can change "dynamically" to suit the problem set, that's all I'm saying.

I'm sorry, but I am *still* unsure about what you mean by "context".
Python
does not need a context object to switch between complex and real number
processing -- it's all done through operator overloading.

I am not saying in all of my dreaming here that typing is not important (down under). Python is called an object based language, yes? Not object oriented, right?

Actually, modern Python is considered object-oriented. I called it
object-based in a *very* early stage, when there was no class statement in
the language. But IIRC it was added before the first public release (or
soon after), although another round of improvements in this area happened
in the early 2000s (eradicating most of the differences between classes
defined by C and Python code).

But we all know that down under the covers (not magic) python uses Classes and instances of classes--objects. We don't pretend that what makes object based languages work is magic? do we?

"We"?

Unifying the number system on a computer does not have to be grounded in paradigms that serviced the industry (and academy) for the past 40 years; mostly because memory was expensive and processing was slow. I suggest that if memory had been cheap, back in the day, and processing had been very fast (as it is today) IEEE 754 1985 floats and doubles would never have been used. We would have used Decimal floating points right from the get-go. Initially, that really is all I'm asking for on the outset--- lets move to default decimal floating point arithmetic for real numbers processing.

Now you've got a specific proposal and we can discuss pros and cons. This is a topic that comes up occasionally and it is indeed a pretty interesting trade-off. I suspect the large (and fast-growing) SciPy community would prefer to keep binary floating point, because that's what their libraries written in Fortran or C++ want, and extra binary/decimal conversions at the boundaries would just slow things down. But I'm really speculating here -- maybe they don't need high-performance conversion between binary and decimal, since (perhaps) they may do their bulk I/O in C++ anyway. Or maybe it varies per application.

In the future I am thinking about systems of
languages that interact with
human speech; understanding
*spoken number* just like they will understand *symbol number.* There is
really no reason (other
than paradigm) that this would not be possible either.

This is the kind of talk that I wish we could keep out of the python-ideas list. (Except for the symbolic algebra, for which various third-party Python libraries already exist.)

Otherwise, Guido, we might all of us just continue to use C and code up our statically bound types by hand using nothing but int, long, float, and double.

Its time to innovate.

Rhetoric again.

Kind regards, BDfL,

marcus

-- --Guido van Rossum (python.org/~guido)

7:13 p.m.

Oscar Benjamin oscar.j.benjamin@gmail.com wrote:

Well, yes. You can also emulate Decimal64 with the appropriate context parameters.

I meant "arbitrary precision facilities by setting context parameters", but probably many people want to keep those.

Due to the compact representation (bid64 or bid128), Decimal64 or Decimal128 would be interesting for doing arithmetic on huge matrices.

I'm still not totally clear here.

Do you mean that decimals created with a decimal literal e.g. 1.123d would be some other kind of decimal object that always used a special arithmetic context so that they behaved like a fixed width Decimal64 type? And then someone who wants to do decimal arithmetic with other contexts would still need to import decimal and do the context stuff themselves?

That would be one option. I'm not sure if it is a *good* option.

Another option is to use the regular Decimal objects with the IEEE context and perhaps add functions to convert to bid64 etc.

Yet another issue is whether a Decimal core type should use the function names from IEEE 754-2008. The names are different, though the behavior is largely the same.

My main point here is that even for the Decimal literal (which seems innocent enough) there is a lot to discuss, since we can specify the semantics only once.

Stefan Krah

10:37 p.m.

On Thursday, March 6, 2014 6:36:46 AM UTC-6, Stefan Krah wrote: > >

As I understand, you are suggesting some kind of number system with symbolic representations that can be converted to decimal if needed.

The topic is so vast that the only chance of it happening is for someone to provide an implementation on PyPI and see if people like it. There are packages for symbolic mathematics like sympy, perhaps that is a start.

```
Yes, that is correct (in a way). I will respond to this better when
```

I respond to Guido, so be sure to read that post.

>

Regarding decimal literals:

```
Good. Dialogue is a good thing. Looking forward to it.
```

Regarding decimal floats by default: >

Perhaps in 4.0, but IMO only with *full*
backing by the large SciPy
and NumPy communities. My guess is that it's not going to happen, not
in the least because the literature for floating point algorithms
(with proofs!) has an overwhelming focus on binary floating point.

```
I know. Again, when I respond to Guido I'll address this a bit.
```

Thanks Stefan, I appreciate your inputs, and your work... keep it up!

marcus

11:23 p.m.

On Thursday, March 6, 2014 11:48:53 AM UTC-6, Guido van Rossum wrote: {snip}

hi Guido, thanks for your responses, your candor, and your kindness; I think dialogue will work, and I promise to keep the rhetoric to a bare minimum, if at all. :)

There really are only two questions I am going to try to answer here, and some of the answer goes back to Stefan's (and others) questions as well, so I hope its helpful.

So, by context you really seem to be referring to some specific state of the program. Perhaps you are even talking about extending the existing notion of numeric context used by Python's Decimal type. But it's not very clear because "you change the context" is also a common term for changing the topic of a discussion.

```
This is what I mean by "context," for this entire discussion, mostly
```

having to do with Decimal and the "context manager" and these two lines:

```
getcontext()
with localcontext(ctx=None) as context_manager
Allow me to back up a bit... the pdeclib module was not difficult
```

for me because of the mathematics (no brag) I can code transcendental functions in my sleep. The hard part of that project last week was coming to grips with the context manger, and the parameterization of context attributes in the Decimal class. Back in the day when I did this work for Rexx at IBM I used NUMERIC DIGITS and NUMERIC FUZZ to emulate (in a crude way) what Decimal is doing correctly with its context, with the context manager. I credit Oscar Benjamin for helping me get up to speed with this concept (and I am so glad he did too, because its key for this entire discussion.

```
Before we go on, allow me to explain my italics of *Number *and
```

*PythonNumber.* These are
each an abstract base class. They are a class that is never instantiated,
but from which all
other "numbers" are derived as objects. (more below)

I'm sorry, but I am *still* unsure about what
you mean by "context".
Python does not need a context object to switch between complex and real
number processing -- it's all done through operator overloading.

```
(more on this below, but I am thinking beyond overloading/
```

actually, virtual functions and templates)

Actually, modern Python is considered object-oriented.

```
Thank you. This has everything to do with this discussion, but
```

allow me to take a humorous break and relate an anecdote... I have had numerous discussions with T.R. and C.A and S.D. and others where in the end python was object based. Ha! Nope, you have seen it right here, the BDfL says its "object oriented," and that's the end of the argument! (ok, tiny rhetoric)

>

Unifying the

number system on a computer does not have to be grounded in paradigms that serviced the industry (and academy) for the past 40 years;

```
{snip}
```

>

Now you've got a specific proposal and we can discuss pros and cons. This is a topic that comes up occasionally and it is indeed a pretty interesting trade-off. I suspect the large (and fast-growing) SciPy community would prefer to keep binary floating point, because that's what their libraries written in Fortran or C++ want, and extra binary/decimal conversions at the boundaries would just slow things down. But I'm really speculating here -- maybe they don't need high-performance conversion between binary and decimal, since (perhaps) they may do their bulk I/O in C++ anyway. Or maybe it varies per application.

```
Here is the main response for this discussion, and I will endeavor
```

to keep the rhetoric down, I promise. This may even be easier to accomplish in 3.x without breaking anyone, or any third party software either; depends

```
In my view numbers are objects (like circles, squares and triangles;
```

in polymorphism discussions).
Numbers are nothing more than objects which have "contexts" just like
circles and squares and triangles
are nothing more than objects that have "contexts". In the following
discussion think in terms of object
oriented (as in Grady Booch) rather than any specific language--- and
certainly not python syntax. um,
think C++ for now, if you must think of any. A *Number *is an abstract
base class with "context" attributes
and pure virtual functions--- it will never be instantiated---and *PythonNumber
*will derive from that; also
an abstract base class. Here is the Class hierarchy I am proposing:

*Number*

```
*PythonNumber*
Decimal
Real <======= this is the default
BinaryIEEE85
Real_b
&c (...) omitted for clarity
Complex
Quaternion
Rational
Fraction
&c--------------------------------------------------
Well, there it is. The idea is that any function or method that
```

takes a *Number* reference
or a *PythonNumber *reference does not have to interrogate what type of
*Number* it is... it just
works (why?) glad you asked, because of polymorphism and a three pointer
hop down a virtual
function table!
If a function or method gets a *Number *parm in as in method(num)
then to get the pretty
print representation of the number is simply num.pretty_print(), or
to get an evaluation
of the number is num.eval(), or to get the string rep of the number is
num.str()... and on and on.
The point is that other parts of the system no longer need to know what
"type" of number object
it is, the right code gets called because of virtual functions, operator
overloading, and the beautiful
virtual function table (all under the covers, not MAGIC, but polymorphism).
Python at present is disjointed when it comes to number as a
concept. In my view numbers
need to be objects, and the Class hierarchy (see my simplified version
above) needs to unify numbers
across the python boards (so to speak) so that "types" are only important
to the implementors, and
so that adding new "types" is easy, concise, and coherent.

```
I realize that this suggestion is over the top in terms of size and
```

weight (Stefan's concern above).
But not really, when you think of conceptualizing the unification of
numbers under *Number *and
*PythonNumber *. There may be other kinds of numbers too, besides these...
like maybe *GmpyNumber*
of *BC_Number*, or others.

```
The bottom line is that we have an object oriented language in front
```

of us that has the software engineering advantages which would allow this kind of hierarchy and conceptualization of a unified number system. Let's leverage our technology for mutual advantage across python communities?

Thanks for your consideration,

Sincerely,

Kind regards,

marcus

11:41 p.m.

On Fri, Mar 7, 2014 at 9:23 AM, Mark H. Harris harrismh777@gmail.com wrote:

```
Well, there it is. The idea is that any
function or method that
```

takes a Number reference or a PythonNumber reference does not have to interrogate what type of Number it is... it just works (why?) glad you asked, because of polymorphism and a three pointer hop down a virtual function table!

That's way too concrete for Pythonic style :)

All you need to do is have each numeric type implement the same operations, and there you are, as the Lord Chancellor said, out of your difficulty at once!

And that's what we already have. You can add two numbers and they'll simply add. In fact, a C++ style "hop down a virtual function table" couldn't handle that. I could make a class hierarchy like you describe, and have each one have an add() method or operator+(), but somewhere along the line, something still has to cope with the fact that it could be given any sort of number - there has to be code someplace that handles the adding of a (Decimal) Real and a Real_b, because some day, someone's going to do it. (The correct response might be to raise TypeError, on the basis that that action merges two separate inaccuracies. But more likely, the correct response is to convert one of them to the other type.)

What does your proposed hierarchy offer that numbers.Number doesn't?

ChrisA

7 Mar
7 Mar

12:58 a.m.

On Thursday, March 6, 2014 4:41:08 PM UTC-6, Chris Angelico wrote:

>

What does your proposed hierarchy offer that numbers.Number doesn't?

hi Chris, numbers.Number is the right concept, but not the correct relationship... in sixty's vernacular, its the right hoo hoo but the wrong tah tah...

Here is the numbers.Number hierarchy:

CLASSES builtins.object Number Complex Real Rational Integral

Compare numbers.Number with my proposal. (I am not wanting to go into semantics at this point, but the numbers.Number model is not unified and has problems. If it were ok, we would not be having this discussion. ) The age old question, "How's that working for you?" Well, not too well.

Chris, its not just the virtual function table, its also operator overloading, and its policy handled by (AI) that intelligently decides what to do if someone tries to add a real_b with a real_d. But, it should not do this:

from pdeclib import * x=1 y=Decimal(.1) x+y Decimal('1.10000000000000000555111512312578270211816')

The policy hidden under the abstraction way down in the guts of the Class hierarchy should make the decision to do this:

====== RESTART ===== from pdeclib import * x=1 y=.1

def add_(x, y): return d(x)+d(y)

add_(x, y) Decimal('1.1')

Please don't pick at this. The point is not the code or the syntax. The point is that the policy in a correctly related Class hierarchy can handle somebody asking the system to add a 1.23b with a 1.23d , and if setup correctly, will just work.

The idea is to unify numbers, so that efficient intelligent processing can occur without "types" or "limits".

I'll say more later... after we see how folks respond.

marcus

12:59 a.m.

Coming to this very late, but I was interested in the first thread...

On 05Mar2014 23:04, Steven D'Aprano steve@pearwood.info wrote:

On Wed, Mar 05, 2014 at 11:29:10AM +0000, Oscar Benjamin wrote:

+1 on that. Although that will depend on how big a slowdown it causes to Python's startup time.

+1 on 123d decimal literals.

and I would also use Fraction literals if available e.g. 1/3F.

Out of curiosity, why F rather than f?

Well, for me, +1 on 123f for "IEEE float" literals. i.e. the floats in play in Python right now.

If ever we get ambiguity on how numbers are done, this may be useful in the transition or experiment phase.

So of course "F" is reasonable for fractions. But how hard does that make the grammar? Because the parser now has to grab the entire "1/3F" to construct the fraction. You can't just stick it in the lexer at that point.

Aside: Unless you do something obtuse, like make "3F" be a regular number with a special internal flag which is treated differently on the right hand side of a division operation: if encoutered, division now returns a Fraction instead of an int or float; this feels like something that could easily have nasty side effects.

And, speaking personally, a big -1 on Mark's "abstract all the numbers". Like other posters, I agree that a number's internal implementation/representation affects its semantics. You can't just wave a wand and say "it should all be abstract"; there will be side-effects.

You can wave a wand and say "from here on in this code, unadorned numbers make Decimals, not IEEE floats".

I'm +0.5 on something like:

from __future__ import decimal_floats

to imply making Decimals from unadorned literals. "__future__" may be the wrong pseudomodule name.

Not least, it makes testing the side-effects much easier because you can tweak a nice big chunk of code without editing it internally.

*syntax* for creating complex numbers,
but no built-in support for exact rationals.)

Harder to parse (see above), smaller use case maybe. It is not a bad idea, just not yet done...

Cameron Simpson cs@zip.com.au

The concept is interesting and well-formed, but in order to earn better than a 'C,' the idea must be feasible. --A Yale University management professor in response to Fred Smith's paper proposing reliable overnight delivery service. (Smith went on to found Federal Express Corp.)

1:17 a.m.

On Fri, Mar 7, 2014 at 10:59 AM, Cameron Simpson cs@zip.com.au wrote:

Aside: Unless you do something obtuse, like make "3F" be a regular number with a special internal flag which is treated differently on the right hand side of a division operation: if encoutered, division now returns a Fraction instead of an int or float; this feels like something that could easily have nasty side effects.

Thanks, now I can take the paper bag off my head. I'm not the only one!

I thought exactly what you say here, that 3F would have to be a magical type of integer. But actually, all it has to be is Fraction(3) and everything will work perfectly.

1/Fraction(3) Fraction(1, 3)

ChrisA

1:17 a.m.

On Thursday, March 6, 2014 4:41:08 PM UTC-6, Chris Angelico wrote:

What does your proposed hierarchy offer that numbers.Number doesn't? >

I just had one more thought along this line; consider this:

from pdeclib import *

s1=sqrt(2.01) s1 Decimal('1.41774468787578244511883132198668766452744') s2=sqrt(d(2.01)) s2 Decimal('1.41774468787578252029556185427085779261123')

s1**2
Decimal('2.00999999999999978683717927196994423866272')
s2**2
Decimal('2.01000000000000000000000000000000000000000')

Which one is right, s1 or s2 ?

Well, clearly s1 is wrong. And yet, s1 was coded by giving the system a normal human number, at a human level, and very innocently s1 is really badly broken but not immediately noticed until we try to square it...

s2 on the other hand is correct, but we had to go through some hoops to make sure that the system received the correct type. The system should be able to make this determination without the user having to determine types.

marcus

1:29 a.m.

On Fri, Mar 7, 2014 at 10:58 AM, Mark H. Harris harrismh777@gmail.com wrote:

But, it should not do this:

from pdeclib import * x=1 y=Decimal(.1) x+y Decimal('1.10000000000000000555111512312578270211816')

The policy hidden under the abstraction way down in the guts of the Class hierarchy should make the decision to do this:

====== RESTART ===== from pdeclib import * x=1 y=.1

def add_(x, y): return d(x)+d(y)

add_(x, y) Decimal('1.1')

Please don't pick at this. The point is not the code or the syntax. The point is that the policy in a correctly related Class hierarchy can handle somebody asking the system to add a 1.23b with a 1.23d , and if setup correctly, will just work.

If you had a literal syntax "0.1d" meaning Decimal("0.1"), this would be solved. The only reason your code seems to work is that you're rounding off your binary floats instead of using them with as much accuracy as they have. You're deceiving yourself that str(float('0.1')) seems to round-trip, and therefore that's the right way to get a decimal value from a float. But with floats completely out of the picture, there's no need to do any of this.

x=1 y=Decimal(".1") # y=.1d x+y Decimal('1.1')

So... I'm +1 for adding a literal syntax for decimals. +1 for adding a stating-the-default for floats (do it straight away, then code that uses it can be backported all the way even when there's a change of default). +0.5 for adding a syntax for fractions; support in principle but the devil's in the details. -1 for trying to unify everything.

ChrisA

1:32 a.m.

On 2014-03-06 23:59, Cameron Simpson wrote:

Coming to this very late, but I was interested in the first thread...

On 05Mar2014 23:04, Steven D'Aprano steve@pearwood.info wrote:

On Wed, Mar 05, 2014 at 11:29:10AM +0000, Oscar Benjamin wrote:

+1 on that. Although that will depend on how big a slowdown it causes to Python's startup time.

+1 on 123d decimal literals.

and I would also use Fraction literals if available e.g. 1/3F.

Out of curiosity, why F rather than f?

Well, for me, +1 on 123f for "IEEE float" literals. i.e. the floats in play in Python right now.

If ever we get ambiguity on how numbers are done, this may be useful in the transition or experiment phase.

So of course "F" is reasonable for fractions. But how hard does that make the grammar? Because the parser now has to grab the entire "1/3F" to construct the fraction. You can't just stick it in the lexer at that point.

I think it would be confusing if there were "f" for "float" and "F" for "fraction". How about "r" for "rationals"?

Aside: Unless you do something obtuse, like make "3F" be a regular number with a special internal flag which is treated differently on the right hand side of a division operation: if encoutered, division now returns a Fraction instead of an int or float; this feels like something that could easily have nasty side effects.

And, speaking personally, a big -1 on Mark's "abstract all the numbers". Like other posters, I agree that a number's internal implementation/representation affects its semantics. You can't just wave a wand and say "it should all be abstract"; there will be side-effects.

You can wave a wand and say "from here on in this code, unadorned numbers make Decimals, not IEEE floats".

I'm +0.5 on something like:

from __future__ import decimal_floats

to imply making Decimals from unadorned literals. "__future__" may be the wrong pseudomodule name.

Not least, it makes testing the side-effects much easier because you can tweak a nice big chunk of code without editing it internally.

*syntax* for creating complex numbers,
but no built-in support for exact rationals.)

Harder to parse (see above), smaller use case maybe. It is not a bad idea, just not yet done...

1:38 a.m.

On Fri, Mar 7, 2014 at 11:17 AM, Mark H. Harris harrismh777@gmail.com wrote:

Well, clearly s1 is wrong. And yet, s1 was coded by giving the system a normal human number, at a human level, and very innocently s1 is really badly broken but not immediately noticed until we try to square it...

s2 on the other hand is correct, but we had to go through some hoops to make sure that the system received the correct type. The system should be able to make this determination without the user having to determine types.

Once again, the problem occurs only because you're using a float literal, and 2.01 can't perfectly round-trip.

Decimal(2.01) Decimal('2.0099999999999997868371792719699442386627197265625')

That's pretty much the value you had (I have a few more digits there, the square rooting and squaring probably cost some accuracy), so I'd say Decimal correctly round-tripped. The problem is the conversion from string (source code) to float to Decimal. You think that str()ing a float gives you a better round-trip, but that works only because you're using relatively small numbers.

Hmm. Is the rounding done by float.__str__() an attractive nuisance? Would it be better to show exactly what's stored, if only to remove the temptation to treat str(f) as "more correct" than f?

ChrisA

1:45 a.m.

On Fri, Mar 7, 2014 at 11:32 AM, MRAB python@mrabarnett.plus.com wrote:

I think it would be confusing if there were "f" for "float" and "F" for "fraction". How about "r" for "rationals"?

Since the string prefixes are all case insensitive, I'd be very much surprised if f and F did different things.

<pedantic>Python doesn't have a syntax for
creating complex numbers; it
has a syntax for creating imaginary numbers.</pedantic>

<pedantic>
>>> type(1j)
<class 'complex'>
</pedantic>ChrisA

2:32 a.m.

From: Mark H. Harris harrismh777@gmail.com Sent: Thursday, March 6, 2014 2:23 PM

Here is the Class hierarchy I am proposing:

Number PythonNumber Decimal Real <======= this is the default BinaryIEEE85 Real_b &c (...) omitted for clarity Complex Quaternion Rational Fraction &c--------------------------------------------------

This kind of hierarchy doesn't work. Most importantly, you can have complex decimal floats and complex binary floats. This is why complex is a class template rather than a class in C++. Also, this is ignoring the numerical tower—rationals are a specialization of reals, just as floats are.

Well, there it is. The idea is that any function or method that takes a Number reference or a PythonNumber reference does not have to interrogate what type of Number it is... it just works (why?) glad you asked, because of polymorphism and a three pointer hop down a virtual function table!

99% of the time you don't need these abstract base classes at all, because duck typing and operator overloading. But when you need it, it's already there, and does everything you're asking for. See below.

If a function or method gets a Number parm in as in method(num) then to get the pretty print representation of the number is simply num.pretty_print(), or to get an evaluation of the number is num.eval(), or to get the string rep of the number is num.str()...

We already have the first and third as str() and repr(), and I'm not sure what the second is supposed to do. What does evaluating a number return other than the number itself?

Python at present is disjointed when it comes to number as a concept. In my view numbers need to be objects, and the Class hierarchy (see my simplified version above) needs to unify numbers across the python boards (so to speak) so that "types" are only important to the implementors, and so that adding new "types" is easy, concise, and coherent.

This is all just plainly false. Numbers are objects. There is a hierarchy of abstract base classes for them—done correctly—in the numbers module. And they handle everything you're asking for, except for one thing: There are no ABCs to distinguish binary vs. decimal floats. But I doubt that's useful for anything. What kind of code can you imagine that does need to distinguish, say, float vs. decimal.Decimal, but doesn't need to distinguish decimal.Decimal from mylib.MyDecimal? They're both inexact floating-point representations of reals.

If the motivation for your whole idea is that you want a class hierarchy like the one in the numbers module but you didn't know that it already existed, then we're done.

2:52 a.m.

From: Chris Angelico rosuav@gmail.com

Sent: Thursday, March 6, 2014 4:29 PM

So... I'm +1 for adding a literal syntax for decimals. +1 for adding a stating-the-default for floats (do it straight away, then code that uses it can be backported all the way even when there's a change of default).

This makes sense if you also add an optional suffix for binary floats at the same time. Otherwise, it would be confusing to talk about the "default" when there's no way to make it explicit, and it would delay any potential change of the default by at least one version, if not more.

Of course there's a lot of room for bikeshedding the suffix. The "f" suffix from C implies 32-bit float as opposed to 64-bit double, which is obviously wrong. The "d" suffix from C might be confusing as distinguishing "binary, not decimal". Maybe "b"?

+0.5 for adding a syntax for fractions; support in principle but the devil's in the details.

What details, other than bikeshedding the exact suffix? If 3r == fraction.Fraction(3), we're done, right?

3:08 a.m.

On Thursday, March 6, 2014 6:38:34 PM UTC-6, Chris Angelico wrote:

Once again, the problem occurs only because you're using a float

literal, and 2.01 can't perfectly round-trip.

Decimal(2.01) Decimal('2.0099999999999997868371792719699442386627197265625')

That's pretty much the value you had (I have a few more digits there, the square rooting and squaring probably cost some accuracy),

hi Chris, yes, you are completely missing the point. We are talking past each other. I DO NOT need you to explain to me why the is working just like its supposed to... its broken and should not work this way !

If you write a number on paper do you write down '2.01' ?

Do you write down d(2.01) ?

Do you write down 2.01d ?

(or) do you write down 2.01 ?

When you punch a number into your TI89 do you punch in {'} {2} {.} {0} {1} {'} ?

(or) do you punch in {2} {.} {0} {1} ?

When I want a square-root from python I want to enter s1=sqrt(2.01)

<====== this is normal
(why?) glad you asked, because that is the human normal thing to do.

Python numbers don't work correctly, because the underlying design is not correct. Everyone sees it, nobody really likes it, but everyone wants to avoid the problem like the proverbial ostrich (only worse , because this ostrich wants me to believe that everything is fine, " Don't pay any attention to that man behind the current... I... am the great and powerful OZ"

marcus

3:09 a.m.

Mark, it feels like you do not understand Python well enough to be able to make sweeping proposals about its reform. Hopefully the responses you are getting will help you make more informed proposals in the future.

On Thu, Mar 6, 2014 at 2:23 PM, Mark H. Harris harrismh777@gmail.comwrote:

> >

On Thursday, March 6, 2014 11:48:53 AM UTC-6, Guido van Rossum wrote: {snip}

hi Guido, thanks for your responses, your candor, and your kindness; I think dialogue will work, and I promise to keep the rhetoric to a bare minimum, if at all. :)

There really are only two questions I am going to try to answer here, and some of the answer goes back to Stefan's (and others) questions as well, so I hope its helpful.

So, by context you really seem to be referring to some specific state of the program. Perhaps you are even talking about extending the existing notion of numeric context used by Python's Decimal type. But it's not very clear because "you change the context" is also a common term for changing the topic of a discussion.

```
This is what I mean by "context," for this entire discussion,
```

mostly having to do with Decimal and the "context manager" and these two lines:

```
getcontext()
with localcontext(ctx=None) as context_manager
Allow me to back up a bit... the pdeclib module was not difficult
```

for me because of the mathematics (no brag) I can code transcendental functions in my sleep. The hard part of that project last week was coming to grips with the context manger, and the parameterization of context attributes in the Decimal class. Back in the day when I did this work for Rexx at IBM I used NUMERIC DIGITS and NUMERIC FUZZ to emulate (in a crude way) what Decimal is doing correctly with its context, with the context manager. I credit Oscar Benjamin for helping me get up to speed with this concept (and I am so glad he did too, because its key for this entire discussion.

```
Before we go on, allow me to explain my italics of *Number *and
```

*PythonNumber.* These are
each an abstract base class. They are a class that is never instantiated,
but from which all
other "numbers" are derived as objects. (more below)

I'm sorry, but I am *still* unsure about
what you mean by "context".
Python does not need a context object to switch between complex and real
number processing -- it's all done through operator overloading.

```
(more on this below, but I am thinking beyond overloading/
```

actually, virtual functions and templates)

Actually, modern Python is considered object-oriented.

```
Thank you. This has everything to do with this discussion, but
```

allow me to take a humorous break and relate an anecdote... I have had numerous discussions with T.R. and C.A and S.D. and others where in the end python was object based. Ha! Nope, you have seen it right here, the BDfL says its "object oriented," and that's the end of the argument! (ok, tiny rhetoric)

>

Unifying the

number system on a computer does not have to be grounded in paradigms that serviced the industry (and academy) for the past 40 years;

```
{snip}
```

>

Now you've got a specific proposal and we can discuss pros and cons. This is a topic that comes up occasionally and it is indeed a pretty interesting trade-off. I suspect the large (and fast-growing) SciPy community would prefer to keep binary floating point, because that's what their libraries written in Fortran or C++ want, and extra binary/decimal conversions at the boundaries would just slow things down. But I'm really speculating here -- maybe they don't need high-performance conversion between binary and decimal, since (perhaps) they may do their bulk I/O in C++ anyway. Or maybe it varies per application.

```
Here is the main response for this discussion, and I will endeavor
```

to keep the rhetoric down, I promise. This may even be easier to accomplish in 3.x without breaking anyone, or any third party software either; depends

```
In my view numbers are objects (like circles, squares and
```

triangles; in polymorphism discussions).
Numbers are nothing more than objects which have "contexts" just like
circles and squares and triangles
are nothing more than objects that have "contexts". In the following
discussion think in terms of object
oriented (as in Grady Booch) rather than any specific language--- and
certainly not python syntax. um,
think C++ for now, if you must think of any. A *Number *is an abstract
base class with "context" attributes
and pure virtual functions--- it will never be instantiated---and *PythonNumber
*will derive from that; also
an abstract base class. Here is the Class hierarchy I am proposing:

*Number*

```
*PythonNumber*
Decimal
Real <======= this is the default
BinaryIEEE85
Real_b
&c (...) omitted for clarity
Complex
Quaternion
Rational
Fraction
&c--------------------------------------------------
Well, there it is. The idea is that any function or method that
```

takes a *Number* reference
or a *PythonNumber *reference does not have to interrogate what type of
*Number* it is... it just
works (why?) glad you asked, because of polymorphism and a three pointer
hop down a virtual
function table!
If a function or method gets a *Number *parm in as in
method(num) then to get the pretty
print representation of the number is simply num.pretty_print(), or
to get an evaluation
of the number is num.eval(), or to get the string rep of the number is
num.str()... and on and on.
The point is that other parts of the system no longer need to know what
"type" of number object
it is, the right code gets called because of virtual functions, operator
overloading, and the beautiful
virtual function table (all under the covers, not MAGIC, but polymorphism).
Python at present is disjointed when it comes to number as a
concept. In my view numbers
need to be objects, and the Class hierarchy (see my simplified version
above) needs to unify numbers
across the python boards (so to speak) so that "types" are only important
to the implementors, and
so that adding new "types" is easy, concise, and coherent.

```
I realize that this suggestion is over the top in terms of size and
```

weight (Stefan's concern above).
But not really, when you think of conceptualizing the unification of
numbers under *Number *and
*PythonNumber *. There may be other kinds of numbers too, besides
these... like maybe *GmpyNumber*
of *BC_Number*, or others.

```
The bottom line is that we have an object oriented language in
```

front of us that has the software engineering advantages which would allow this kind of hierarchy and conceptualization of a unified number system. Let's leverage our technology for mutual advantage across python communities?

Thanks for your consideration,

Sincerely,

Kind regards,

marcus

-- --Guido van Rossum (python.org/~guido)

3:12 a.m.

On 2014-03-07 01:52, Andrew Barnert wrote:

From: Chris Angelico rosuav@gmail.com

Sent: Thursday, March 6, 2014 4:29 PM

So... I'm +1 for adding a literal syntax for decimals. +1 for adding a stating-the-default for floats (do it straight away, then code that uses it can be backported all the way even when there's a change of default).

This makes sense if you also add an optional suffix for binary floats at the same time. Otherwise, it would be confusing to talk about the "default" when there's no way to make it explicit, and it would delay any potential change of the default by at least one version, if not more.

Of course there's a lot of room for bikeshedding the suffix. The "f" suffix from C implies 32-bit float as opposed to 64-bit double, which is obviously wrong. The "d" suffix from C might be confusing as distinguishing "binary, not decimal". Maybe "b"?

Calling the floating-point class "float" implies 32-bit float as opposed to 64-bit double, doesn't it? :-)

+0.5 for adding a syntax for fractions; support in principle but the devil's in the details.

What details, other than bikeshedding the exact suffix? If 3r == fraction.Fraction(3), we're done, right?

3:16 a.m.

On 7/03/2014 1:17 p.m., Chris Angelico wrote:

On Fri, Mar 7, 2014 at 10:59 AM, Cameron Simpson cs@zip.com.au wrote:

Aside: Unless you do something obtuse, like make "3F" be a regular number with a special internal flag

I thought exactly what you say here, that 3F would have to be a magical type of integer. But actually, all it has to be is Fraction(3) and everything will work perfectly.

I think Cameron was talking about using 'F' for both 'binary
floating point' *and* 'fraction', which would indeed lead to
madness.

-- Greg

3:19 a.m.

On Thursday, March 6, 2014 8:09:02 PM UTC-6, Guido van Rossum wrote:

Mark, it feels like you do not understand Python well enough to be able to

make sweeping proposals about its reform.

hi Guido, ouch.

You can't be serious. You have not commented yet... are you happy with this:

from decimal import Decimal a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123') <==== does this not bother you at all ?

```
... even though we both know why its doing this, doesn't it
bother you
```

a little?

marcus

3:26 a.m.

From: MRAB python@mrabarnett.plus.com

Sent: Thursday, March 6, 2014 6:12 PM

On 2014-03-07 01:52, Andrew Barnert wrote:

Of course there's a lot of room for bikeshedding the suffix. The "f" suffix from C implies 32-bit float as opposed to 64-bit double, which is obviously wrong. The "d" suffix from C might be confusing as distinguishing "binary, not decimal". Maybe "b"?

Calling the floating-point class "float" implies 32-bit float as opposed to 64-bit double, doesn't it? :-)

Sure. It's not as strong an implication as an "f" suffix, because many languages (and applications) have types named float, while "0.3f" is a lot more C-family-specific. Which means "float" wasn't a perfect choice—but it was still probably the best choice. And it's certainly possible that "f" is the best choice for the suffix despite this problem. If there were an obvious best answer that had no problems, there wouldn't be any room for bikeshedding.

3:51 a.m.

On Thursday, March 6, 2014 8:09:02 PM UTC-6, Guido van Rossum wrote:

Mark, it feels like you do not understand Python well enough to be able to

make sweeping proposals about its reform.

hi Guido, I'm sorry, but isn't that the whole point? The person who notices that something isn't working quite right (that would be me) comes to court, as it were, before those who are supposed to know intimately how everything works (that would be you) and in good faith asks politely for the "people in the know" to correct the difficulty.

You know what, you're right--- I'm a dumb cluck who doesn't know anything about the intricacies of python yadda yadda yadda... but you know what else ? >

I hate this:

from decimal import Decimal a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123')

... and I think the people who DO know intimately how python works, should do something to fix it.

I'd like to help, if you'd let me.

marcus

3:52 a.m.

On 07Mar2014 00:32, MRAB python@mrabarnett.plus.com wrote:

On 2014-03-06 23:59, Cameron Simpson wrote:

Coming to this very late, but I was interested in the first thread...

On 05Mar2014 23:04, Steven D'Aprano steve@pearwood.info wrote:

On Wed, Mar 05, 2014 at 11:29:10AM +0000, Oscar Benjamin wrote:

and I would also use Fraction literals if available e.g. 1/3F.

Out of curiosity, why F rather than f?

Well, for me, +1 on 123f for "IEEE float" literals. i.e. the floats in play in Python right now. [...] So of course "F" is reasonable for fractions. But how hard does that make the grammar? Because the parser now has to grab the entire "1/3F" to construct the fraction. You can't just stick it in the lexer at that point.

I think it would be confusing if there were "f" for "float" and "F" for "fraction". How about "r" for "rationals"?

Cameron Simpson cs@zip.com.au

If you do not read the paper, you are uninformed. If you do read the paper, you are misinformed. - Mark Twain

3:53 a.m.

From: Mark H. Harris harrismh777@gmail.com Sent: Thursday, March 6, 2014 6:19 PM

[snipping out of order]

>>> from decimal import Decimal

a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123') <==== does this not bother you at all ?

... even though we both know why its doing this, doesn't it bother you a little?

That's not a problem with Python's number system, it's that Decimal(.1) is not the right way to write what you want.

The only solution without changing Python is to train end-users to write something correct, like Decimal('.1').

The obvious solution for changing Python is to make it easier to create Decimal numbers correctly and/or harder to create them incorrectly. For example, a decimal suffix, as already proposed before this thread, would completely solve the problem:

>>> a = 1d >>> b = .1d >>> a+b 1.1d

Of course the exact suffix (or other syntax) is up for bikeshedding, as is the possibility of one day changing the default from binary floats to decimal floats, but other than those trivial details, tada. But there's no need for anything more radical, like some amorphous idea to "unify Python numbers".

On Thursday, March 6, 2014 8:09:02 PM UTC-6, Guido van Rossum wrote:

Mark, it feels like you do not understand Python well enough to be able to make sweeping proposals about its reform.

hi Guido, ouch.

You proposed that Python should handle numbers in an OO way, with numbers being real objects, instances of classes, with a hierarchy including abstract base classes; all of this is already there in Python.

You went off on a long digression about how you could implement this using the details of C++-style inheritance, when Python has a completely (and more powerful) different solution to inheritance that has already been used to solve this problem.

You proposed some complicated AI-based solution to solve the problem of using separate number classes in a single expression, even though Python (almost exactly like C++, in this case) has already solved that problem with operator overloading.

(And note that Python is flexible enough that third-party libraries can easily insert new types like quaternions, matrices, symbolic expressions, etc. into the hierarchy in a way that's transparent to end users. I can multiply a NumPy matrix of float64 values by the builtin in 2 just by writing "m * 2", and it works exactly the way you'd want it to. It's hard to imagine that would be even feasible with an AI-based solution, but with the current design, that's the easiest part of NumPy.)

There are some ideas in your posts that are worth responding to, but I think it's perfectly fair for Guido to decide it's not worth digging through the mass of ignorance about Python's basic design to find the nuggets that can be rejected for more specific reasons instead of just dismissed.

3:58 a.m.

On 07Mar2014 15:16, Greg Ewing greg.ewing@canterbury.ac.nz wrote:

On 7/03/2014 1:17 p.m., Chris Angelico wrote:

On Fri, Mar 7, 2014 at 10:59 AM, Cameron Simpson cs@zip.com.au wrote:

Aside: Unless you do something obtuse, like make "3F" be a regular number with a special internal flag

I thought exactly what you say here, that 3F would have to be a magical type of integer. But actually, all it has to be is Fraction(3) and everything will work perfectly.

I think Cameron was talking about using 'F' for both 'binary
floating point' *and* 'fraction', which would indeed lead to
madness.

Well, only because I hadn't thought through to having it be a literal

Cameron Simpson cs@zip.com.au

The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself. Therefore all progress depends on the unreasonable man. - George Bernard Shaw

4:12 a.m.

"Mark H. Harris" harrismh777@gmail.com writes:

On Thursday, March 6, 2014 8:09:02 PM UTC-6, Guido van Rossum wrote:

Mark, it feels like you do not understand Python well enough to be able to make sweeping proposals about its reform.

hi Guido, I'm sorry, but isn't that the whole point? The person who notices that something isn't working quite right (that would be me) comes to court, as it were, before those who are supposed to know intimately how everything works (that would be you) and in good faith asks politely for the "people in the know" to correct the difficulty.

No, that's not the point of this forum. The point of this forum is to
present ideas for improvement to Python and to *defend* those ideas
robustly against critique, in order to converge on concrete proposals.

To do that, you need to have a pretty good understanding of Python as it currently is, especially in the domain of the change you're proposing, in order to robustly defend an idea for improvement.

I hate this: […]

If you want a forum for general discussion where complaints lacking a good understanding of Python can be bounced around, this isn't it. You can try the general discussion forum for Python

URL:https://mail.python.org/mailman/listinfo/python-list.

But beware that even there, complaints about how Python behaves aren't
going to be well received if you show a wilful ignorance of that
behaviour. Mere ignorance is fine, of course: we all start out ignorant
of any given topic. But wilful ignorance isn't a good foundation for
expecting change. It's better to take the complaint as motivation to
*learn* why Python behaves this way, to see if your assumptions may not
be flawed.

... and I think the people who DO know intimately how python works, should do something to fix it.

You'll need to form a much more concrete understanding of why it is the way it is, and exactly what behavioural change you want, and an understanding of the costs and benefits of that change, before such an expectation would in any way imply an obligation on others to do what you want. This forum isn't the place to begin with that.

-- \ “You say “Carmina”, and I say “Burana”, You say “Fortuna”, and | `\ I say “cantata”, Carmina, Burana, Fortuna, cantata, Let's Carl | _o__) the whole thing Orff.” —anonymous | Ben Finney

4:29 a.m.

On 06Mar2014 18:08, Mark H. Harris harrismh777@gmail.com wrote:

On Thursday, March 6, 2014 6:38:34 PM UTC-6, Chris Angelico wrote: Once again, the problem occurs only because you're using a float

literal, and 2.01 can't perfectly round-trip.

Decimal(2.01) Decimal('2.0099999999999997868371792719699442386627197265625')

That's pretty much the value you had (I have a few more digits there, the square rooting and squaring probably cost some accuracy),

hi Chris, yes, you are completely missing the point. We are talking past each other. I DO NOT need you to explain to me why the is working just like its supposed to... its broken and should not work this way !

If you write a number on paper do you write down '2.01' ?

Do you write down d(2.01) ?

Do you write down 2.01d ?

(or) do you write down 2.01 ?
[...]
When I want a square-root from python I want to enter s1=sqrt(2.01)

<====== this is normal

So essentially you are saying: when I write Python code I do not want to have to put in all this special notation to get the actual value which I naturally mean.

And if you write 2.01, you mean 2 + 1/100, without roundoff because you do not want to be working in a system which would round that off. i.e. a Decimal internal representation because you're writing a number in base 10.

To my mind you want a Python _parser_ mode, because inside python, given the various numeric classes abounding, the actual _mechanisms_ already exist.

So really you want to have a way of saying to the parser: in the code, 2.01 is not transformed into an IEEE float with some loss of precision, it is transformed into a Decimal floating point number, with no loss of precision.

There are, it seems to me, two basic ways to approach this.

The easy one is give Python something like:

from __the_far_far_future__ import Decimal_numbers

and have decimal literals converted into those instead of Python "floats".

The alternative is the implement a Python class, let us call it "AbtractNumber", which stores the literal text you put in the programme code "2.01" and converts it, _when used_, to a suitable python type with the right semantics.

Either would get you code where:

```
x = 2.01
```

is stored without loss of precision, and may get you the behaviour you're after.

Which of these more closely matches your desires?

Please try to be precise. Examples don't quite do it without some rather precise accompanying lagunage which says what specific things the example is meant to illustrate.

If one of my two suggestions above is very close to what you're after, please present an example where one suggestion does what you want and where the other would not, with an explaination of why.

Cameron Simpson cs@zip.com.au

Careful and correct use of language is a powerful aid to straight thinking, for putting into words precisely what we mean necessitates getting our own minds quite clear on what we mean. - W.I.B. Beveridge

4:45 a.m.

On Thu, Mar 06, 2014 at 04:17:55PM -0800, Mark H. Harris wrote:

I just had one more thought along this line; consider this:

from pdeclib import * s1=sqrt(2.01) s1 Decimal('1.41774468787578244511883132198668766452744') s2=sqrt(d(2.01)) s2 Decimal('1.41774468787578252029556185427085779261123')

s1**2
Decimal('2.00999999999999978683717927196994423866272')
s2**2
Decimal('2.01000000000000000000000000000000000000000')

If you skip the conversion to Decimal, you actually get the right answer using floats:

py> (2.01**0.5)**2
2.01

So the problem here isn't the binary float, but that Decimal
by default has *too much precision* and consequently it ends
up keeping digits that the user doesn't care about:

py> from decimal import Decimal as D
py> (D.from_float(2.01)**D("0.5"))**2
Decimal('2.009999999999999786837179272')

Floats have about 14 significant base-10 figures of precision (more in base-2), so if we tell Decimal to use the same, we should get the same result:

py> import decimal
py> ct = decimal.getcontext()
py> ct.prec = 14
py> (D.from_float(2.01)**D("0.5"))**2
Decimal('2.0100000000000')

Decimal is not a panacea. Both Decimal and binary floats have the same limitations, they just occur in different places for different numbers. All floating point numbers have these same issues. Fixed point numbers have different issues, rationals have their own issues, and symbolic computations have a different set of issues.

Computer maths is a leaky abstraction. No matter what you do, how you implement it, the abstraction leaks. Not even Mathematica can entirely hide the fact that it is computing rather than performing a Platonic ideal of mathematics.

-- Steven

4:53 a.m.

On Thursday, March 6, 2014 8:53:35 PM UTC-6, Andrew Barnert wrote: > >

The only solution without changing Python is to train end-users to write something correct, like Decimal('.1').

```
hi Andrew, yes, that is the problem. And, to be fair, that example
```

is not really the worst because it might be expected that the user should know how to construct Decimals, and could be educated to construct them properly--> Decimal('0.1'); I concur. It is worse in these two scenarios (and others too): sqrt(.1) sin(.1) No one would expect that the user should know to quote the number---when is that ever done? QED this is broken. Again, we know perfectly well why its happening (I am not ignorant) but its not right.

>

The obvious solution for changing Python is to make it easier to create Decimal numbers correctly and/or harder to create them incorrectly. For example, a decimal suffix, as already proposed before this thread, would completely solve the problem:

```
>>> a = 1d
>>> b = .1d
>>> a+b
1.1d
```

```
Yes, and at a bare minimum, that is the immediate change I have been
```

asking for, for now; nothing more.

```
I answered Guido's questions in hopes that he might be willing to
```

dialogue --- not dismiss.

You proposed that Python should handle numbers in an OO way, with numbers being real objects, instances of classes, with a hierarchy including abstract base classes; all of this is already there in Python.

```
Yes, it is... but its not designed to use decimal floating point by
```

default... in order to do that the entire OO setup will have to be changed (what Guido called a sweeping reform). Its like if we want to use floating point decimals in the 21st century, using python, we have to duck tape modules on and then educate users in the correct input of numbers. Seems convoluted to me.

>

You went off on a long digression about how you could implement this using the details of C++-style inheritance, when Python has a completely (and more powerful) different solution to inheritance that has already been used to solve this problem.

```
No, I did not. I answered Guido's questions regarding context as
```

clearly as I could. If python has a more powerful way to handle this situation, gladly do it! I will be happy as a clam to beta test or help with the coding.

>

You proposed some complicated AI-based solution to solve the problem of using separate number classes in a single expression, even though Python (almost exactly like C++, in this case) has already solved that problem with operator overloading.

```
No, I did not. I suggested that unifying numbers in an (AI) way
```

could solve this problem (conceptually) by
regarding all numbers as *PythonNumbers. *Decimals should not only be
default, they should be integrated, not
tacked on with duck tape.

>

(And note that Python is flexible enough that third-party libraries can easily insert new types like quaternions, matrices, symbolic expressions, etc. into the hierarchy in a way that's transparent to end users. I can multiply a NumPy matrix of float64 values by the builtin in 2 just by writing "m * 2", and it works exactly the way you'd want it to. It's hard to imagine that would be even feasible with an AI-based solution, but with the current design, that's the easiest part of NumPy.)

```
That's nice for you. Because sqrt(.23709) does not behave as I
```

expect, sadly, I have to train my users to enter sqrt('0.23709').

>

There are some ideas in your posts that are worth responding to,

```
Thank you. If a user goes to the time and trouble to present an
```

idea clearly, I would expect the responders to respect the effort and respond to the points that make sense.

```
Andrew, I respect you for taking the time to dialogue, I appreciate
```

it. Thanks.

marcus

5:05 a.m.

On Thursday, March 6, 2014 9:45:47 PM UTC-6, Steven D'Aprano wrote:

Decimal is not a panacea. Both Decimal and binary floats have the same limitations, they just occur in different places for different numbers. All floating point numbers have these same issues. Fixed point numbers have different issues, rationals have their own issues, and symbolic computations have a different set of issues.

```
hi Steven, yes, I concur. My primary objection (for immediate
```

concern) is that we have now a very fast module for doing decimal floating point math with less of the issues you speak of. Doing decimal floating point math by default, or at a minimum, providing a way to enter decimal numbers 1.234d would do a lot to alleviate much of the perceived difficulty with float issues.

>

Computer maths is a leaky abstraction. No matter what you do, how you implement it, the abstraction leaks. Not even Mathematica can entirely hide the fact that it is computing rather than performing a Platonic ideal of mathematics.

```
Again, I concur. Leaky is one thing... but being mired in 40-year-old
```

paradigms because IEEE 754 1985 floats |doubles are so entrenched just doesn't make sense to me, and should be corrected.

```
I am not insisting on Guido's "sweeping" reform. I'm just trying to
```

make like a little easier for number munching and get folks to be forward thinking about moving to a decimal based floating point system of number for python by default---sometime.

```
Thank you for your response, Steven.
```

5:16 a.m.

On Fri, Mar 7, 2014 at 12:52 PM, Andrew Barnert abarnert@yahoo.com wrote:

From: Chris Angelico rosuav@gmail.com

Sent: Thursday, March 6, 2014 4:29 PM

So... I'm +1 for adding a literal syntax for decimals. +1 for adding a stating-the-default for floats (do it straight away, then code that uses it can be backported all the way even when there's a change of default).

This makes sense if you also add an optional suffix for binary floats at the same time. Otherwise, it would be confusing to talk about the "default" when there's no way to make it explicit, and it would delay any potential change of the default by at least one version, if not more.

Yep. That's what I mean (when I said "float"s up there, I meant the current type 'float', aka binary floating point). The suffix won't have any effect; it'll be like u"string" in Py3, explicitly stating the default, and added for the exact same reason.

+0.5 for adding a syntax for fractions; support in principle but the devil's in the details.

What details, other than bikeshedding the exact suffix? If 3r == fraction.Fraction(3), we're done, right?

Precisely that detail. Is it 3r? 3F? Something else? Would it look tidier as a prefix instead of a suffix? But if that can be resolved, it'd be good to have a syntax for fractions.

I'm only +0.5 on that, as rationals aren't as big an advantage over the status quo as decimal is. It's a lot less common to see:

Fraction(1/3) Fraction(6004799503160661, 18014398509481984)

than the oddities of decimal.Decimal construction that we're seeing here. Having a numeric suffix for decimal literals will be much more beneficial to the language, imo; if the bikeshedding of Fraction literals is problematic, I'd not be against doing decimal (and binary float) tags one version, and leaving Fraction for another version. ('Course, it might all work out perfectly, in which case great! Add 'em all at once.)

ChrisA

5:17 a.m.

On Thursday, March 6, 2014 9:12:32 PM UTC-6, Ben Finney wrote:

But beware that even there, complaints about how Python behaves aren't going to be well received if you show a wilful ignorance of that behaviour. Mere ignorance is fine, of course: we all start out ignorant of any given topic. But wilful ignorance isn't a good foundation for expecting change.

```
Ben, excuse me, but you are out of line here. I have not shown
```

will full ignorance in any of this discussion (None). I am not ignorant of the system, nor am I ignorant of the underlying caveats. I have studied here, and I have a pretty good handle on how things are working. When I have been confused I asked questions, read, experimented, and researched. I will not have it here said that I have displayed will full ignorance, nor should anyone believe it.

```
I have a good understanding of why it is... I just don't like why it
```

is... those are two very different things. I am fully aware of the issues created by IEEE 754 1985 floats and doubles. This is 2014. I would expect that python would be using IEEE 754 2008 not only for specification, but also for moving towards decimal floating point arithmetic by default. Continuing to stick with a 1985 standard, or even an IEEE 854 1987 standard, is putting all python communities behind the eight ball going forward into the 21st Century. This is not will full ignorance. Its eyes-wide-open thoroughly knowledgable concern with a genuine "will" to see the right thing done... because its the right thing to do.

marcus

>

5:27 a.m.

"Mark H. Harris" harrismh777@gmail.com writes:

On Thursday, March 6, 2014 9:12:32 PM UTC-6, Ben Finney wrote:

But beware that even there, complaints about how Python behaves aren't going to be well received if you show a wilful ignorance of that behaviour. Mere ignorance is fine, of course: we all start out ignorant of any given topic. But wilful ignorance isn't a good foundation for expecting change.

```
Ben, excuse me, but you are out of line here. I have not shown
```

will full ignorance in any of this discussion (None).

I didn't mean to imply that, though I can see how the above can be read that way. My apologies for giving that impression.

Nevertheless, the warning stands for those who wish to bring a complaint in this forum or the “python-list” forum.

-- \ “Probably the toughest time in anyone's life is when you have | `\ to murder a loved one because they're the devil.” —Emo Philips | _o__) | Ben Finney

5:29 a.m.

On Fri, Mar 7, 2014 at 3:17 PM, Mark H. Harris harrismh777@gmail.com wrote:

On Thursday, March 6, 2014 9:12:32 PM UTC-6, Ben Finney wrote:

>

But beware that even there, complaints about how Python behaves aren't going to be well received if you show a wilful ignorance of that behaviour. Mere ignorance is fine, of course: we all start out ignorant of any given topic. But wilful ignorance isn't a good foundation for expecting change.

```
Ben, excuse me, but you are out of line here. I have not shown
```

will full ignorance in any of this discussion (None).

Hey hey, I'd be careful of calling someone out like that... might want to be a little humble and respectful here :)

The main problem here is that you and the Python interpreter have different expectations about a string of digits in the source code. You expect them to be represented exactly; the language specifies that they be represented with the float type. Changing how the language interprets something as fundamental as "number with decimal point in it", even if unassailably correct, is a backward-incompatible change. (The full unification of the int and long types wasn't effected until Python 3, even though that change is unlikely to break much code.)

If someone wants to push for it, write the PEP, go through all the details, then it'd be possible to have this for Python 3.5:

0.1d == Decimal("0.1") True

But you're not going to have that effect for untagged numbers without a long deprecation period; probably we're talking Python 4000, which may not ever even exist. It'd be possible to have a __future__ directive that switches around the two types, so decimal is the default and binary floats have to be tagged, but that could cause so much confusion that it'd itself be the subject of massive controversy. (Although there would be precedent for it. "from __future__ import unicode_literals" didn't destroy Py2 code readability.)

ChrisA

5:32 a.m.

On 06Mar2014 19:53, Mark H. Harris harrismh777@gmail.com wrote:

On Thursday, March 6, 2014 8:53:35 PM UTC-6, Andrew Barnert wrote: [... lots of stuff, snipped along with Mark's replies ...]

You proposed some complicated AI-based solution to solve the problem of using separate number classes in a single expression, even though Python (almost exactly like C++, in this case) has already solved that problem with operator overloading.

```
No, I did not. I suggested that unifying numbers in an (AI) way
```

could solve this problem (conceptually) by
regarding all numbers as *PythonNumbers. *Decimals should not only be
default, they should be integrated, not
tacked on with duck tape.

This sounds to me like keeping the original numeric text around and only turning it into some efficient-for-a-machine representation when it comes time to work on it.

One issue with that is that when the work occurs, it can be far from where the number was defined, and the correct internal representation might be poorly chosen.

(And note that Python is flexible enough that third-party libraries can easily insert new types like quaternions, matrices, symbolic expressions, etc. into the hierarchy in a way that's transparent to end users. I can multiply a NumPy matrix of float64 values by the builtin in 2 just by writing "m * 2", and it works exactly the way you'd want it to. It's hard to imagine that would be even feasible with an AI-based solution, but with the current design, that's the easiest part of NumPy.)

```
That's nice for you. Because sqrt(.23709) does not behave as I
```

expect, sadly, I have to train my users to enter sqrt('0.23709').

That's partly because .23709, in current python, becomes an IEEE float with some loss of precision because binary fractions and base-10 fractions do not round trip.

But also partly because sqrt() (in the pure mathematical sense) often produces transcendental numbers, which are not representation by any fixed precision intergal base notation - effectively IEEE floats and Decimal floats are fractions/rationals.

So sqrt() will, for almost all numbers, involve loss of precision no matter what base your backing storage is: base 2 as in IEEE float or base 10 as in a Decimal. Steven (or Stephen) has already pointed this out to you; perhaps it has been missed.

And going back (eg sqrt(2.01)*sqrt(2.01) ==> not-quite-2.01) just extends this loss of precision.

This is an inherent problem unless sqrt() doesn't convert to a "concrete" type, and instead just lurks as some symbolic representation, so that any expression involving it becomes a progressively more elaboration representation of an algebraic expression. Which may be correct, but only until you try to evaluate it to get a concrete number again.

There are some ideas in your posts that are worth responding to,

```
Thank you. If a user goes to the time and trouble to present an
```

idea clearly, I would expect the responders to respect the effort and respond to the points that make sense.

Many people have. You should see the short shrift some ideas receive.

I think part of the problem is not your wish for a "number" but that lack of a concrete proposal to implement it.

Cameron Simpson cs@zip.com.au

The Usenet is not the real world. The Usenet usually does not even resemble the real world. - spaf@cs.purdue.edu

5:51 a.m.

On Thursday, March 6, 2014 10:27:39 PM UTC-6, Ben Finney wrote: >

```
Ben, excuse me, but you are out of line here. I have not shown
```

will full ignorance in any of this discussion (None).

I didn't mean to imply that, though I can see how the above can be read that way. My apologies for giving that impression.

Nevertheless, the warning stands for those who wish to bring a complaint in this forum or the “python-list” forum.

hi Ben, forgive me, I misunderstood you. No problem, nor hard feelings. Chris is absolutely correct when he asks for humble respect. I absolutely respect you guys, and I'm here to learn as much as anything. Thank you for your insights.

Kind regards, marcus

6:30 a.m.

On Mar 6, 2014, at 19:53, "Mark H. Harris" harrismh777@gmail.com wrote:

On Thursday, March 6, 2014 8:53:35 PM UTC-6, Andrew Barnert wrote:

The only solution without changing Python is to train end-users to write something correct, like Decimal('.1').

```
hi Andrew, yes, that is the problem. And, to be fair, that example
is not really the worst
```

because it might be expected that the user should know how to construct Decimals, and could be educated to construct them properly--> Decimal('0.1'); I concur. It is worse in these two scenarios (and others too): sqrt(.1) sin(.1)

No, that's not the same problem but worse, it's a completely different problem.

In the first case, the user is trying to specify 0.1 as a decimal number, which is exactly representable, just not the way he's entered it.

In these cases, the numbers are irrational, and therefore inherently impossible to represent exactly. It doesn't matter whether you use decimal floats or binary floats.

```
No one would expect that the user should
know to quote the number---when is that ever done?
```

What does quoting have to do with anything? Do you not understand why Decimal('.1') works? It's not because Python is a weakly-typed language that allows you to use strings as numbers, but because Decimal has a constructor that takes strings, for a specific and well-documented reason. Quoting here would just give you a TypeError. And nothing in your proposal(s) would change that.

```
QED this is broken. Again, we know
perfectly well why its happening (I am not ignorant) but its not right.
```

The only way to fix this second problem is to use a symbolic representation instead of a numeric one.

The good news is that Python's design makes that pretty easy to add on--as SymPy demonstrates. Your proposal would actually make this kind of add-on much harder.

The obvious
solution for changing Python is to make it easier to create Decimal numbers correctly
and/or harder to create them incorrectly.

Yes, and at a bare minimum, that is the immediate change I have been asking for,
for now; nothing more.

And people have agreed with that, and proposed feasible extensions to it. Presenting it as the first step toward some radical and ill-formed transformation of the whole language weakens the case for this suggestion. Implying that it would solve problems (like handling irrational numbers) that it obviously can't also weakens the case.

```
I answered Guido's questions in hopes that
he might be willing to dialogue --- not dismiss.
```

He was willing to dialogue, as evidenced by his initial replies. It was only after you demonstrated your ignorance of basics fundamentals of Python (as a user, not even about its implementation) and math/numerics, and implied that you had no interest in correcting that ignorance, that he dismissed you. And he has every right to do so. He's the one donating his free time to make a great language for you (and many others) to use, not the other way around.

You proposed that Python should handle numbers in an OO way, with numbers being real objects, instances of classes, with a hierarchy including abstract base classes; all of this is already there in Python.

```
Yes, it is... but its not designed to use decimal floating point by
default... in order to do that the entire OO
```

setup will have to be changed (what Guido called a sweeping reform).

Nonsense. Python already has classes for both binary and decimal floats. They both fit into the hierarchy properly. They both interact with other types the way they should. Changing which one you get from the literal "0.1" would be a simple change to the parser, and have no effect whatsoever to the OO setup.

You went off
on a long digression about how you could implement this using the details of C++-style
inheritance, when Python has a completely (and more powerful) different solution to
inheritance that has already been used to solve this problem.

No, I did not. I answered Guido's questions regarding context as clearly as I
could. If python has a more powerful
way to handle this situation, gladly do it! I will be happy as a clam to beta test or
help with the coding.

It's already been done, years ago, so nobody has to do it. There's already an OO system that works, with abstract and concrete classes, and with a rich operator overloading system. And it's already been used to give you all of the abstract and concrete numeric types you want, and they do the things you asked for in this section, all without having to do any jumps through virtual pointer tables.

```
No, I did not. I suggested that unifying numbers in an (AI) way
could solve this problem (conceptually) by
```

regarding all numbers as PythonNumbers.

So you didn't suggest a complicated AI-based solution, you suggested a complicated AI-based solution?

Decimals should not only be default, they should be integrated, not tacked on with duck tape.

How does that have anything to do with the first half of this paragraph?

And in what way are Decimals "tacked on with duck tape"? They're instances of Number, and of Real. They act like numbers of other types, including interacting properly with other types like int. What is missing from the Decimal type and the ABCs in Number that makes you think we need a radical change?

If all you're suggesting is moving Decimal from the decimal module to builtins and/or adding parser support for decimal literals, those are not sweeping changes, they're both very simple changes. (That doesn't necessarily mean they're _desirable_ changes, but that's another argument--an argument nobody can actually begin until you make it clear whether or not that's what you're suggesting, which can't happen until you learn enough about using Python to know what you're suggesting.)

```
That's nice for you. Because sqrt(.23709) does not behave as I
expect, sadly, I have to train my users to enter sqrt('0.23709').
```

How do you expect it to behave? Again, using decimal floats here would make no difference. Instead of needing an infinite number of binary digits, you need an infinite number of decimal digits. Dividing infinite by log2(10) still leaves infinity. If you're trying to fix that, you're not trying to fix Python, you're trying to fix irrational numbers. This list cannot help you with that. Prayer is the only option, but medieval mathematicians tried that and didn't get very far, and eventually we had to accept that there are numbers that cannot be represented finitely.

There are some ideas in your posts that are worth responding to,

```
Thank you. If a user goes to the time and trouble to present an idea
clearly, I would expect the responders
```

to respect the effort and respond to the points that make sense.

```
Andrew, I respect you for taking the time to dialogue, I appreciate it.
Thanks.
```

marcus

7:01 a.m.

On Thursday, March 6, 2014 11:30:47 PM UTC-6, Andrew Barnert wrote: >

```
No one would expect that the user should
know to quote the
```

number---when is that ever done?

What does quoting have to do with anything? Do you not understand why Decimal('.1') works? It's not because Python is a weakly-typed language that allows you to use strings as numbers, but because Decimal has a constructor that takes strings, for a specific and well-documented reason. Quoting here would just give you a TypeError. And nothing in your proposal(s) would change that.

I understand precisely what it happening. It is just the opposite of what you are implying (that I'm ignorant) which you actually state later. I full well know that the quoted string is forcing the right constructor to be called for the Decimal object. I am an experienced OO programmer and have many years under my belt with C++, Smalltalk, and Java. Please don't be pedantic with me, nor imply that I am ignorant. It does not become you, and it irritates me... besides, it gets in the way of discussion. My point is not about loose typing, nor about Decimal constructors, ... my point is that an average user entering a number into a sqrt() function is not going to realize that they need to enter a quoted string in order to force the string constructor on the Decimal Class !! They are going to want to enter this --> sqrt( .789 ) not this sqrt( '0.789' )

I know how it works. I'm asking for a reasonable response to the need for normal usability.

7:11 a.m.

On Thu, Mar 06, 2014 at 06:19:56PM -0800, Mark H. Harris wrote:

from decimal import Decimal a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123') <==== does this not bother you at all ?

Of course it bothers people a little. It bothers me. It also bothers me when I'm too hot wearing a coat and too cold when I take it off, but sometimes that's how the universe works.

For the first few releases of Decimal, it prohibited direct conversion of floats specifically to avoid that issue:

# Python 2.5

py> decimal.Decimal(2.01) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.5/decimal.py", line 648, in __new__ "First convert the float to a string") TypeError: Cannot convert float to Decimal. First convert the float to a string

But that turned out to be more of a nuisance than what it was trying to protect from, so starting in Python 2.7 Decimal now supports direct and exact conversion from float.

The problem is, you are focused on one application for numeric computing
to the exclusion of all else, specifically using Python as an
interactive calculator. But in practice, for many uses, nobody typed in
2.01 and nobody has any expectation that it will be exactly the value
2.01. Rather, the value will be the result of some calculation, and
there is *absolutely no reason to think* that the decimal number 2.01
will be more accurate than the binary number
10.101000111101011100001010001111010111000010100, or for that matter,
the base-7 number 2.0462046204620462. The difference between those three
representations is usually minor compared to the actual measurement
errors of the initial data and the rounding errors from the calculation.

-- Steven

7:24 a.m.

On Thursday, March 6, 2014 11:30:47 PM UTC-6, Andrew Barnert wrote: >

```
I answered Guido's questions in hopes that
he might be willing to
```

dialogue --- not dismiss.

He was willing to dialogue, as evidenced by his initial replies. It was only after you demonstrated your ignorance of basics fundamentals of Python (as a user, not even about its implementation) and math/numerics, and implied that you had no interest in correcting that ignorance, that he dismissed you. And he has every right to do so. He's the one donating his free time to make a great language for you (and many others) to use, not the other way around.

```
Andrew, I demonstrated no such thing. This is the second time you
```

have accused me
of being ignorant of basics and fundamentals. My answers in no way
(implied or otherwise)
ignorant, they were well defined, carefully articulated, and implied an
ernest interest in understanding
and cooperation. Your *ad hominem* comments about my so-called ignorance
do not become you
and are beginning to annoy me... I don't see any reason for it.

```
I know how the system works... and I think it can be improved. My
```

ideas are unpopular, I realize that, but that does not mean that I am ignorant, nor does it mean that I don't understand python fundamentals. I had a professor one time who told us, "You know when you have won the argument when they attack you personally (or call you names)".

```
Why don't we keep the discussion on the details relevant to the topic
```

and leave the personal attacks alone?

marcus

7:39 a.m.

On Friday, March 7, 2014 12:11:21 AM UTC-6, Steven D'Aprano wrote:

For the first few releases of Decimal, it prohibited direct conversion of floats specifically to avoid that issue:

# Python 2.5

py> decimal.Decimal(2.01) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.5/decimal.py", line 648, in __new__ "First convert the float to a string") TypeError: Cannot convert float to Decimal. First convert the float to a string

But that turned out to be more of a nuisance than what it was trying to protect from, so starting in Python 2.7 Decimal now supports direct and exact conversion from float.

hey Steven, whoa...! that is useful, and very interesting news. That almost makes me want to cry.... Wait a minute, I'm going to check it out, ... son of a gun, you're right. My Py2.6.1 does not allow me to enter this sqrt( .789 ). What-da-ya-know........ Here is the output:

from pdeclib import * sqrt(.789) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pdeclib.py", line 243, in sqrt sqr=Decimal(x).sqrt() File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/decimal.py", line 652, in __new__ "First convert the float to a string") TypeError: Cannot convert float to Decimal. First convert the float to a string

So, "ignorant boy" finds out that the community has come to court with unclean hands !

Everyone (even the core devs) know that this little problem sucks, and they've tried to fix it in different ways, and yet when I bring it up its like (hypocrisy) I'm the one with the problem--- when in fact all along everyone knows that this little IED was going to go off and make a mess. ~nice.

<sigh> Steven, I honor you and thank you for being honest and coming clean on this.

Geeze... I'm going to bed.

9:11 a.m.

Mark H. Harris harrismh777@gmail.com wrote:

You can't be serious. You have not commented yet... are you happy with this:

from decimal import Decimal a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123') <==== does this not bother you at all ?

You can set a trap:

getcontext().traps[FloatOperation] = True b = Decimal(.1) Traceback (most recent call last): File "<stdin>", line 1, in <module> decimal.FloatOperation: [<class 'decimal.FloatOperation'>]

Stefan Krah

10:17 a.m.

On Thu, Mar 6, 2014 at 6:51 PM, Mark H. Harris harrismh777@gmail.comwrote:

I hate this:

from decimal import Decimal a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123')

... and I think the people who DO know intimately how python works, should do something to fix it.

The problem here--and the problem with the "default number type is decimal" is that what you want fixed is "unfixable." As a number of other people have noted, what you are demanding is that numbers have finite sized and exact representations, which they simply don't and can't in computers with finite memory.

Your example, Mark, is merely one where you have misspelled your Python code:

from decimal import Decimal as D D('1') + D('.1') Decimal('1.1')

Fix your spelling error, and in this case you get what you want. However, what about this:

D(1/3) * 3 Decimal('0.9999999999999999444888487687')

There's no way to fix that spelling. Yes, you might be able to play with a decimal.Context in a way that gets this one example to happen to round the way you want it to, but no context will do so generically for every similar expected identity under reciprocal operators.

By coincidence, this works under binary FB *because* of the limited
precision of floats:

1/3 * 3 1.0

But that's just luck, basically, it's not because we've managed to precisely represent 1/3 as a binary float. We don't have to look all that far for a problem case:

1/49 * 49 0.9999999999999999

Of course, we do have Fractions, which will always do the right thing under the reciprocal division and multiplication operators:

F('1/3') * 3 Fraction(1, 1)

Don't misspell this either, of course:

F(1/3) * 3 Fraction(18014398509481983, 18014398509481984)

However, even if Fraction were to become the default numeric type for Python, and preserve the reciprocity of div/mul, that wouldn't help us any under the hoped reciprocals of sqrt() and square. Similarly, of course, for trigonometric identities and various other numeric functions that produce irrational answers. E.g.:

from fractions import Fraction as F
sqrt(F('1/3'))**2
0.3333333333333333
F(sqrt(F('1/3'))**2)
Fraction(6004799503160661, 18014398509481984)

Well, that answer is a binary floating point; we leave Fraction land pretty easily in Python. The float we get, of course, is something that's not exactly 1/3 (but is pretty close).

Even if we imagined some future version of Python that did sqrt() purely as a Fraction without converting to IEEE 754, we STILL couldn't ever have an exact identity. There simply does not exist any Fraction that can accurately represent sqrt(F('1/3')); there are only rational numbers that are "pretty close." Maybe this one, for example:

F(sqrt(F('1/3'))) Fraction(1300077228592327, 2251799813685248)

Maybe this hypothetical Python 4000 that didn't do the conversion to floating point would produce a different approximation, but it would always be an approximation, not the Real (irrational) answer.

-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

11:12 a.m.

On Thu, Mar 06, 2014 at 07:53:15PM -0800, Mark H. Harris wrote: [...]

because it might be expected that the user should know how to construct Decimals, and could be educated to construct them properly--> Decimal('0.1'); I concur. It is worse in these two scenarios (and others too): sqrt(.1) sin(.1)

Decimal won't save you in those two cases.

For square roots, the result is usually a surd, an irrational number. The only way to store surds exactly is to store it symbolically. Should we try to turn Python into Mathematica?

In the case of sines, the same applies. While it is possible to calculate exact rational values for some angles, but for most the calculation rapidly becomes untenable, and in general trigonometric values are irrational, like surds.

```
No one would expect that the user should
know to quote the
```

number---when is that ever done?

OF COURSE WE DO. This is a programming language, not a calculator.

We expect users -- programmers -- to know how to write functions, define classes, import modules, use regular expressions, call external processes, and so on. In other words, we expect them to know how to program.

```
QED this is broken.
```

No it is not.

Another factor you are not taking into account is that there is a reason that the computer industry has standardised on base-2 floats rather than

- 30 years ago, it was quite common to find applications using some variant of Decimal, perhaps using BCD (binary coded decimal) as their native numeric format. Going back even further, computers were built using decimal-based hardware:

http://en.wikipedia.org/wiki/Decimal_computer

But people moved to binary for multiple reasons:

it's cheaper;

it's faster;

it's less surprising.

For example, between 1 and 100000, about 12% of integer-valued floats fail the "1/(1/n) == n" test, but over 51% of the integer-valued Decimals:

py> from decimal import Decimal as D py> sum(1/(1/n) != n for n in range(1, 100001)) 11846 py> sum(1/(1/D(n)) != n for n in range(1, 100001)) 51665

Likewise we can test how many fail the "(1/n)*n == 1" test:

py> sum((1/n)*n != 1 for n in range(1, 100001))
13117
py> sum((1/D(n))*n != 1 for n in range(1, 100001))
36806

13% for floats versus 36% for Decimals.

One more to prove it isn't a fluke: the "sqrt(n)**2 == n" test:

py> sum((n**0.5)**2 != n for n in range(1, 100001))
49544
py> sum((n**D("0.5"))**2 != n for n in range(1, 100001))
71303

That's three for three in favour of binary floats.

[...]

```
Yes, and at a bare minimum, that is the
immediate change I have been
```

asking for, for now; nothing more.

You're not going to get an immediate change, no matter how solid the case you ask for, no matter how logical the argument. Python 3.4 is not receiving any new features. The earliest this could come out is 3.5, in about 18 months or so.

```
I answered Guido's questions in hopes that
he might be willing to
```

dialogue --- not dismiss.

You've had at least three emails from Guido. That's three more than most radical or wild ideas receive. The fact that so many people are continuing to discuss this with you is proof that we recognise that computer maths is hard and unintuitive. But just because you have identified a problem doesn't mean that your answer is a solution.

[...]

```
No, I did not. I suggested that unifying numbers in an (AI) way
```

could solve this problem (conceptually) by
regarding all numbers as *PythonNumbers. *Decimals should not only be
default, they should be integrated, not
tacked on with duck tape.

It's statements like this that strongly suggest that you don't really
understand how numbers work in computer programming. Or the practical
limitations of what is *possible*. And as soon as you start talking
about using an AI to decide what value the user intended, you've entered
crackpot territory.

Speaking of AIs, perhaps you ought to see what Wolfram Alpha is capable of:

https://www.wolframalpha.com/input/?i=%28square+root+of+2.01%29+to+the+power...

Not surprisingly for something with Mathematica behind it, it gets the exact right answer. Do you understand why Python the language cannot duplicate Mathematica's abilities with symbolic maths? Hint: it's not because it isn't technically possible. See, for example, libraries like sympy.

-- Steven

11:16 a.m.

On Fri, Mar 07, 2014 at 05:11:21PM +1100, Steven D'Aprano wrote: [...]

there is *absolutely no reason to think* that
the decimal number 2.01
will be more accurate than the binary number
10.101000111101011100001010001111010111000010100, or for that matter,
the base-7 number 2.0462046204620462. The difference between those three
representations is usually minor compared to the actual measurement
errors of the initial data and the rounding errors from the calculation.

Er, apparently I cannot convert fractions into base 7 even using Python. That should be 2.003300330033 in base 7. The number I gave was actually 2.1.

-- Steven

11:39 a.m.

On Fri, Mar 07, 2014 at 11:38:34AM +1100, Chris Angelico wrote:

Hmm. Is the rounding done by float.__str__() an attractive nuisance?

It's not *rounding* precisely. Starting in Python 3.1, the float
__repr__ will display the shortest decimal string that doesn't change
the float's value.

Reading the issue tracker history for this change is informative:

http://bugs.python.org/issue1580

To the guys who worked on that, I take my hat off to you all.

-- Steven

11:57 a.m.

On Thu, Mar 06, 2014 at 06:08:28PM -0800, Mark H. Harris wrote:

If you write a number on paper do you write down '2.01' ? Do you write down d(2.01) ? Do you write down 2.01d ?

Sometimes I write down 2.01 subscript 10. That's just a different way of spelling 2.01d.

(That's the trouble with rhetorical questions. Sometimes people will give an answer other than what you were expecting.)

(or) do you write down 2.01 ?

When you punch a number into your TI89 do you punch in {'} {2} {.} {0} {1} {'} ? (or) do you punch in {2} {.} {0} {1} ?

For what it's worth, the TI-89 stores numbers using a decimal floating point format.

-- Steven

12:05 p.m.

On Fri, Mar 7, 2014 at 9:39 PM, Steven D'Aprano steve@pearwood.info wrote:

On Fri, Mar 07, 2014 at 11:38:34AM +1100, Chris Angelico wrote:

Hmm. Is the rounding done by float.__str__() an attractive nuisance?

It's not *rounding* precisely. Starting in Python 3.1, the float
__repr__ will display the shortest decimal string that doesn't change
the float's value.

Reading the issue tracker history for this change is informative:

http://bugs.python.org/issue1580

To the guys who worked on that, I take my hat off to you all.

That effect, yes. Let's call it "the magic of float.__str__", because it really is pretty amazing.

But it's still post-processing magic. It means that strings appear to round-trip through floats, as long as you're a long way within the available precision; but as soon as you do operations, that ceases to be the case. I think it's great for display, but is putting that into __repr__ (at least, they do appear to be the same) an attractive nuisance, in that it encourages people to treat float("...") as a true representation?

Don't get me wrong, it's a really *awesome* feature. It just happens
to have been partially responsible for this thread, which I think is
approaching 300 posts.

ChrisA

12:06 p.m.

On Fri, Mar 7, 2014 at 10:05 PM, Chris Angelico rosuav@gmail.com wrote:

Don't get me wrong, it's a really *awesome*
feature. It just happens
to have been partially responsible for this thread, which I think is
approaching 300 posts.

Whoops, no. This one's only approaching 100. It's the "should midnight be true or false" that's approaching 300. Sorry!

ChrisA

3:59 p.m.

Chris Angelico writes:

That effect, yes. Let's call it "the magic of float.__str__", because it really is pretty amazing.

But it's still post-processing magic. It means that strings appear to round-trip through floats, as long as you're a long way within the available precision; but as soon as you do operations, that ceases to be the case. I think it's great for display, but is putting that into __repr__ (at least, they do appear to be the same) an attractive nuisance, in that it encourages people to treat float("...") as a true representation?

What makes you think they need more encouragment?

Seriously, as one data point, I don't think having more "human" representations encourages me the think of floating point results as the product of arithmetic on real numbers. I don't think anybody who knows how tricky "floating point" arithmetic can be is going to be fooled by the "pretty eyes" of a number represented as "2.0" rather than "1.99999999999999743591".

4:09 p.m.

On Sat, Mar 8, 2014 at 1:59 AM, Stephen J. Turnbull stephen@xemacs.org wrote:

Seriously, as one data point, I don't think having more "human" representations encourages me the think of floating point results as the product of arithmetic on real numbers. I don't think anybody who knows how tricky "floating point" arithmetic can be is going to be fooled by the "pretty eyes" of a number represented as "2.0" rather than "1.99999999999999743591".

Fair enough. I just remember reading, back in my really REALLY early days with GW-BASIC, an explanation of why 3.2# (the hash made it double-precision) came out as whatever-it-did. Went into a full explanation of the nature of binary floating point, and the issue was forced to your attention because just about _any_ value that wasn't a neat multiple of a (negative) power of two would do that.

You can lead a programmer to docs, but you can't make him understand.

ChrisA

8:01 p.m.

On Friday, March 7, 2014 4:12:35 AM UTC-6, Steven D'Aprano wrote:

It's statements like this [ AI language ] that strongly suggest that you

don't really
understand how numbers work in computer programming. Or the practical
limitations of what is *possible*. And as soon as you start talking
about using an AI to decide what value the user intended, you've entered
crackpot territory.

hi Steven, fair enough. When I refer to AI all I'm really stating is that some form of computational intelligence needs to be employed beyond simple copying, &c. An intelligent decision needs to be used; that's all. I'll stop using it (AI) , its not helpful.

Also, thank you for continuing to discuss this, it helps me grow, and it may lead to an understanding or change. That's a good thing.

Here is the main post for today, from me, having slept on this and putting some things back into historical timeframe. Sometime before Py2.7.x it was not possible to promote a binary float to a decimal float; the system threw an exception (convert to string).

First, that was the correct way to handle this issue from the inception of decimal.Decimal. Throw an exception and let everyone know that there is a problem; then let the user figure out how to solve it.

It is not possible to correctly promote a binary float to a decimal float. The decision (in Py2.7?) was to NOT throw an exception and to copy the binary float "exactly" into the decimal float.

Second, that was a bad decision--really--IMHO. It was the wrong decision for two reasons 1) it denies what is inherently poor about binary floating point representation in the first place, and 2) further compounds the problem by denying what is the benefit of using Decimal floating point in the first place. Not to mention, it gives the wrong answer; from a decimal floating point perspective it gives a VERY wrong answer. (I realize most science and engineering solutions only require 3-4 digits of accuracy---heck, I used to do that work on a slide rule).

The point here is that binary floats should not be promoted to decimal floats by using "exact" copy from binary float representation to decimal float representation. This reminds me of the guys at three mile island who turned off the alarms because they were annoying.

Steven, I am going to try to go a little further without AI speak...

What needs to happen is that
binary floats need to be "correctly" promoted to decimal floats as
appropriate. This must not happen
by simple copying (that does not work). There needs to be some policy in
place that will correctly
"set" the decimal float value based on intelligence, not just simple
copying. ( It may not be possible, or
it may require some funky copy b float-->reduce context round-->set
decimal . I'm still thinking about it.

To make decimal the default, and to handle all numbers correctly giving the SciPy community the ability to use binary floats easily too (without breaking codes) will require a complete rewrite of numbers.Number and maybe some other things. Unifying the numbers concept in python is desirable but not practical at this point... I understand that; I get it. I am not asking anyone for this kind of "sweeping" reform at this point... just think forward to the possibility. Please.

Stefan's suggestion to set a trap is an excellent one, and I will play around with that a bit. Although I was initially asking for a method to more easily enter a literal decimal, and that is still important, I think really what is called for here considering the history is to rescind the decision to promote binary floats to decimal floats through an "exact" copy without flagging an error, or even giving a warning.

I am remembering my Zen of python... special cases aren't special enough to break the rules, although practicality beats purity, errors should never pass silently.

Anyway, I know you wonderful guys will be thinking hard about this (evidenced by the discussion so far) and I leave it in your competent hands. I too will be thinking about it, perhaps adding some coding in the near future that will be helpful. Again, I wish to thank Stefan Krah for his fine work on decimal.Decimal, without which none of this discussion would be worth having in the first place.

Kind regards, marcus

>

8:20 p.m.

Mark,

I don't know if it has occurred to you, but several of the rhetorical forms you have used are pretty upsetting to many Python devs. (I can give you a list offline.) It would really help the discussion if you tried to keep your rhetoric in check. (A few other participants might also want to focus more on the topic and less on Mark.)

Now I'd like to restate the core issue in ways that should appeal to Python devs. I am describing the status quo in Python 3.4, since that's the starting point for any evolution of Python. Please bear with me as I describe the status quo -- I find it necessary as a context for any future proposals.

Python's parser recognizes a few different types of number literals: integers (e.g. 42), "decimal point" notation (e.g. 3.14), "exponential notation" (e.g. 2.1e12), and any of these followed by 'j'. There are also binary, octal and hex notations for integers (e.g. 0b1010, 0o12, 0xa). The parser, by design, does not have any understanding of the context in which these numbers are used -- just as it doesn't know about the types of variables (even when it's obvious -- e.g. after seeing "x = 4" the parser does not keep track of the fact that x is now an integer with the value 4, although it does remember it has seen an assignment to x).

What the parser does with those number literals (as with string literals and with the keywords None, True, False) is that it creates an object of an appropriate type with an appropriate value. This object is then used as a value in the expression in which it occurs.

The current algorithm for creating the object is to create an int (which is
implemented as an exactly represented arbitrary-precision integer) when the
literal is either binary/octal/hex or decimal without point or exponent, a
Python float (which is an object wrapping an IEEE 754 double) when a
decimal point or exponent is present, and a Python complex (which wraps
*two* IEEE doubles) when a trailing 'j' is seen.

Note that negative numbers are not literals -- e.g. -10 is an expression which applies the unary minus operator to an integer object with the value

- (Similarly, IEEE inf and NaN don't have literals but requires evaluating an expression, e.g. float('inf').)

Not all numbers come from literals -- most come from expressions such as x+y, some are read from files or other input media. (E.g. the struct module can "read" IEEE floats and doubles from byte strings containing their "native" binary encoding, and float() with a string argument can interpret decimal numbers with optional decimal point and/or exponent.)

The clever thing about expressions (in Python as in many languages) is that the overloading of operators can make the "right" think happen when numbers of different types are combined. Consider x+y. If x and y are both ints, the result is an int (and is computed exactly). If either is a float and the other an int, the result is a float. If either is a complex and the other is an int or float, the result is a float.

If you are defining your own number-ish type in Python or C code, you can
make it play this game too, and this is how the current Decimal and
Fraction types work. Python's operator overloading machinery is flexible
enough so that e.g. Fraction can say "if a Fraction and an int are added,
the result is a Fraction; but if a Fraction and a float are added, the
result is a float." (Because Python is dynamic, it would be *possible* to
define a Fraction type that returns a plain int for results whose
denominator is 1, but the stdlib's Fraction type doesn't do this.)

Most of the time, the built-in types are conservative in their return type
(by which I mean that adding two ints returns an int, not a float), but
there are a few exceptions: int/int returns a float (because returning an
int would require truncation or a compound (div, mod) result), and the **
(power) operator also has a few special cases (int**negative_int and
negative_float**non_integer).

Now let's move on to the problems, which begin with the current float type. Given the representation it is obvious that various limitations exist; results may have to be truncated to fit into 64 bits, and conversion to and from decimal cannot always preserve the exact value. It turns out that the truncation of results doesn't bother most users (everyone knows what a typical hand calculator does for 1/3), but the issues around conversion to and from decimal often trip over users on their initial forays into learning the language. The improvement we made to the output conversion (dropping digits that don't affect round-tripping) take some of the sting out of this, but Python newbies still excitedly point out some of the unavoidable anomalies like 1.1 + 2.2 ==> 3.3000000000000003 when they first discover it.

So what to do about it?

If we aren't too worried about strict backwards compatibility, there's an easy answer that should shut up the newbies: change the parser so that when it sees a number with a decimal point or an exponent it returns a Decimal instance, and make a bunch of other adjustments to match. (E.g repr() of the Decimal 3.14 should return '3.14' instead of "Decimal('3.13')".) The float() builtin should also return a Decimal (we should probably just rename the whole type to 'float'). The math module will have to be rewritten. We should probably preserve the existing binary float under some other name, perhaps requiring an import. We'll have to decide what to do about complex numbers. (One option would be to remove supporting them in the parser.) The details can be worked out in a PEP. I don't expect this process to be easy or without controversies, but I'm confident we could come up with a good design.

But there are big problems with such a proposal.

Those newbies perhaps aren't Python's most important user population. (And for many of them it is actually a moment of enlightenment on their way to becoming experts, once they hear the explanation.) The problems with binary floats don't affect most actual calculations (never mind 1.1+2.2, once you've seen what 1/3 looks like you switch all your output to some kind of rounded format). Many of the problems with floating point that actually matter (such as truncation, or infinities, or NaN) don't go away just by switching everything to Decimal.

For many Python programs the switch to decimal would not matter at all. For example, in all my own recent coding (which focuses on network I/O), the only uses I've had for float is for timekeeping (which is inherently approximate) and to prints some percentages in a report. Such code wouldn't be affected at all -- it wouldn't break, but it wouldn't be any simpler either. Decimal/binary simply isn't an issue here.

However, for the growing contingent of scientists who use Python as a replacement for Matlab (not Mathematica!), it could be a big nuisance. They don't care about decimal issues (they actually like binary better) and they write lots of C++ code that interfaces between CPython's internal API and various C++ libraries for numeric data processing, all of which use IEEE binary. (If anything, they'd probably want a 32-bit binary float literal more than a decimal float literal.) Python already defines some macros for converting between standard Python float objects and C++ doubles (e.g. PyFloat_AS_DOUBLE), so technically we could support source-code compatibility for these users, but performance would definitely suffer (currently that macro just returns the C++ double value already present in the object; it would have to be changed to call a costly decimal-to-binary macro).

There is also the huge effort of actually implementing such a proposal. It's not insurmountable, but I estimate it would be at least as much work as the str->unicode conversion we did for Python 3.

All in all I just don't see the stars aligned for this proposal. (Nor for anything even more sweeping like representing 1/3 as a fraction or some other symbolic representation.)

So what to do instead? A few simper proposals have been made.

Maybe we can add a new literal notation meaning 'decimal'; that would be a relatively straightforward change to the parser (once the new decimal extension module is incorporated), but it would not do much to avoid surprising newbies (certainly you can't go teaching them to always write 3.14d instead of 3.14). However, it would probably help out frequent users of Decimal. (Then again, they might not be using literals that much -- I imagine the source of most such programs is user or file input.)

I'm not excited about a notation for Fraction -- the use cases are too esoteric.

Maybe we can fix the conversion between Decimal and float (if this is really all that matters to Mark, as it appears to be from his last email -- I'd already written most of the above before it arrived). Could it be as simple as converting the float to a string using repr()? Given the smarts in the float repr() that should fix the examples Mark complained about. Are there any roadblocks in the implementation or assumptions of the Decimal type here? Perhaps the default Decimal context being set for a much higher precision makes it philosophically unacceptable?

I think I've said as much as I can, for now.

-- --Guido van Rossum (python.org/~guido)

9:28 p.m.

New subject: Fwd: Python Numbers as Human Concept Decimal System

On Thu, Mar 6, 2014 at 6:51 PM, Mark H. Harris harrismh777@gmail.comwrote:

I hate this:

from decimal import Decimal a=Decimal(1) b=Decimal(.1) a+b Decimal('1.100000000000000005551115123')

... and I think the people who DO know intimately how python works, should do something to fix it.

The problem here--and the problem with the "default number type is decimal" is that what you want fixed is "unfixable." As a number of other people have noted, what you are demanding is that numbers have finite sized and exact representations, which they simply don't and can't in computers with finite memory.

Your example, Mark, is merely one where you have misspelled your Python code:

from decimal import Decimal as D D('1') + D('.1') Decimal('1.1')

Fix your spelling error, and in this case you get what you want. However, what about this:

D(1/3) * 3 Decimal('0.9999999999999999444888487687')

There's no way to fix that spelling. Yes, you might be able to play with a decimal.Context in a way that gets this one example to happen to round the way you want it to, but no context will do so generically for every similar expected identity under reciprocal operators.

By coincidence, this works under binary FB *because* of the limited
precision of floats:

1/3 * 3 1.0

But that's just luck, basically, it's not because we've managed to precisely represent 1/3 as a binary float. We don't have to look all that far for a problem case:

1/49 * 49 0.9999999999999999

Of course, we do have Fractions, which will always do the right thing under the reciprocal division and multiplication operators:

F('1/3') * 3 Fraction(1, 1)

Don't misspell this either, of course:

F(1/3) * 3 Fraction(18014398509481983, 18014398509481984)

However, even if Fraction were to become the default numeric type for Python, and preserve the reciprocity of div/mul, that wouldn't help us any under the hoped reciprocals of sqrt() and square. Similarly, of course, for trigonometric identities and various other numeric functions that produce irrational answers. E.g.:

from fractions import Fraction as F
sqrt(F('1/3'))**2
0.3333333333333333
F(sqrt(F('1/3'))**2)
Fraction(6004799503160661, 18014398509481984)

Well, that answer is a binary floating point; we leave Fraction land pretty easily in Python. The float we get, of course, is something that's not exactly 1/3 (but is pretty close).

Even if we imagined some future version of Python that did sqrt() purely as a Fraction without converting to IEEE 754, we STILL couldn't ever have an exact identity. There simply does not exist any Fraction that can accurately represent sqrt(F('1/3')); there are only rational numbers that are "pretty close." Maybe this one, for example:

F(sqrt(F('1/3'))) Fraction(1300077228592327, 2251799813685248)

Maybe this hypothetical Python 4000 that didn't do the conversion to floating point would produce a different approximation, but it would always be an approximation, not the Real (irrational) answer.

-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

9:39 p.m.

On Wed, Mar 5, 2014, at 7:30, Oscar Benjamin wrote:

In C and some other languages the f suffix indicates a numeric literal that has type "float" e.g. "6.0f". You can use upper case there as well but the convention is lower case and to include the .0 when it's an integer. I just though that 3F looks sufficiently distinct from the way it's typically done in C.

How about 3r?

On the subject of suffixes, it might be worth considering that they often use "d" for "double" as well. C# uses "m" (for money) for decimal.

9:45 p.m.

On Wed, Mar 5, 2014, at 21:21, Mark H. Harris wrote:

"Then, and only then, a function that requires PI or
half_PI

will first check to see if __gpi__ has been cached and if so pull it
back, or if not calculate it (once). If the context
does not change then the function can take half of some whopping value of
PI, for the duration of the context, and
use that, but if not needed for the context will never bind it !

What if you want a number type to represent a fraction of pi exactly, so you can take its sine and get the square root of a rational number, then square it to get the rational number?

Or to take the sine of an arc tangent of a rational number.

9:48 p.m.

On Thu, Mar 6, 2014, at 7:46, Stephen J. Turnbull wrote:

Steven D'Aprano writes:

The problem is that fractions can be unbounded in memory,

Seminumerical Algorithms has a few problems on "floating slash", ie a representation of numbers where the total memory allocated to a fraction is fixed, but the amounts allocated to numerator and denominator are variable. I don't know if it has ever been tried in practice, though. And of course any reasonable fixed size would have very limited ability to express very large or very small magnitudes.

Or, for that matter, you could limit both the numerator and the denominator to some maximum value - when an intermediate result exceeds it (won't be more than the square of the maximum for most operations), you find the closest representable value.

10:24 p.m.

On Mar 7, 2014, at 11:01, "Mark H. Harris" harrismh777@gmail.com wrote:

Here is the main post for today, from me, having slept on this and putting some things back into historical timeframe. Sometime before Py2.7.x it was not possible to promote a binary float to a decimal float; the system threw an exception (convert to string).

First, that was the correct way to handle this issue from the inception of decimal.Decimal. Throw an exception and let everyone know that there is a problem; then let the user figure out how to solve it.

The decision was discussed at the time, and all the pros and cons were hashed out. If you're not willing to read that discussion, your opinion that the change was a mistake is worth exactly as much as that of any individual user who asked for the change.

It is not possible to correctly promote a binary float to a decimal float. The decision (in Py2.7?) was to NOT throw an exception and to copy the binary float "exactly" into the decimal float.

There is no such thing as copying a binary float exactly into a decimal float. Decimal float values are not a superset of binary float values; in either direction, you have to round. Which is what Python 2.7+ does.

Steven, I am going to try to go a little further without AI speak... What needs to happen is that binary floats need to be "correctly" promoted to decimal floats as appropriate. This must not happen by simple copying (that does not work). There needs to be some policy in place that will correctly "set" the decimal float value based on intelligence, not just simple copying. ( It may not be possible, or it may require some funky copy b float-->reduce context round-->set decimal . I'm still thinking about it.

Python is _already_ doing exactly what you're asking for. Each binary float is rounded to the closest possible decimal float.

And it's hard to imagine what else it _could_ be doing. There is no closer decimal than the closest decimal, so any "smarter" algorithm would either do the exact same thing, or be worse.

It's true that this "doesn't work" to solve your problem, but that's only because you're trying to solve an impossible problem. Once you've rounded a number inexactly (whether to binary or decimal), you can't get the lost data back.

To make decimal the default, and to handle all numbers correctly giving the SciPy community the ability to use binary floats easily too (without breaking codes) will require a complete rewrite of numbers.Number and maybe some other things.

Why do you keep saying that even after it's been repeatedly explained to you that it wouldn't? Number and its subclasses do not care about the representation of the number. Guido explained to you exactly what would and wouldn't need to be changed, and the abstract base classes do not enter into it.

11:18 p.m.

On Friday, March 7, 2014 3:24:06 PM UTC-6, Andrew Barnert wrote: >

It is not possible to correctly promote a binary float to a decimal float. The decision (in Py2.7?) was to NOT throw an exception and to copy the binary float "exactly" into the decimal float.

There is no such thing as copying a binary float exactly into a decimal float. Decimal float values are not a superset of binary float values; in either direction, you have to round. Which is what Python 2.7+ does.

Andrew, you are deliberately side-stepping the point. You apparently disagree with Steven D, and others, who have already stated repeatedly on this list that on 2.7+ binary float literals are copies "exactly" to decimal float representation! That is what is causing this entire problem, sir. That is the reason for this:

from
decimal import Decimal
Decimal(.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')

<====== this is not a rounding problem

It is a copy problem, and it should not be allowed. It was a bad decision to approach the problem this way, and the correct response to it is to not allow it, throw an exception, and let the user fix it. Otherwise, as I've stated earlier, there needs to be a policy that promotes the binary floating point representation to decimal floating point representation without the mess from the 16th digit out.... and, that may not be possible (needs to be discussed, and researched).

It is clear from Guido's response earlier this afternoon that he understands the problem, and has some good ideas for how to proceed. I am happy with that, and will keep monitoring the news to see how things go.

Thanks for your feedback.

marcus

11:33 p.m.

On Fri, Mar 7, 2014 at 1:24 PM, Andrew Barnert abarnert@yahoo.com wrote:

There is no such thing as copying a binary float exactly into a decimal float. Decimal float values are not a superset of binary float values; in either direction, you have to round. Which is what Python 2.7+ does.

Presuming enough digits of precision, every binary float could be represented precisely in decimal. Decimal is base 10, which factors to 2 and 5, meaning all powers of 1/2 and 1/5 can be precisely represented. Binary floats can only be powers of 1/2.

Chris

8 Mar
8 Mar

12:12 a.m.

On Fri, Mar 7, 2014 at 2:33 PM, Chris Kaynor ckaynor@zindagigames.comwrote:

Presuming enough digits of precision, every binary float could be represented precisely in decimal. Decimal is base 10, which factors to 2 and 5, meaning all powers of 1/2 and 1/5 can be precisely represented. Binary floats can only be powers of 1/2.

This is half true. Yes, the factors of 10 are 2 and 5. So every number that can be exactly represented in base-2 or in base-5 can also be represented exactly in base-10 (given enough digits.

The problem is that the converse is not the case. So in the case Mark presents several times:

from decimal import Decimal a = 0.1 # store a binary float Decimal(a) Decimal('0.1000000000000000055511151231257827021181583404541015625')

That decimal number *is* an exact representation of the value
referenced by
'a'. So if that's all you want, no problem exists.

But the problem that does exist is that there is *no* binary floating point
representation that is exactly the same numeric value as Decimal('0.1').
This would be true, not only for float32 or float64 values that Python has
(depending on the machine architecture and compilation switches), it would
also be true if we used float128, or float256, or float8192 numbers.

And again, while that one example might lead you to think that decimal floating point is simply better, we don't need to go far to find a value like Fraction('1/3') that simply cannot have an exact representation in decimal either, no matter how many digits of precision we decide to use.

12:14 a.m.

On Mar 7, 2014, at 14:18, "Mark H. Harris" harrismh777@gmail.com wrote:

Andrew, you are deliberately side-stepping the point. You apparently disagree with Steven D, and others, who have already stated repeatedly on this list that on 2.7+ binary float literals are copies "exactly" to decimal float representation! That is what is causing this entire problem, sir.

What's causing the problem is that 0.1 and float(0.1) can both be represented exactly in Decimal--and that they are different. You've lost information in converting to float (I may have stated that the wrong way around last time; if so, apologies), and you cannot regain that information when converting back.

If you assume people will only ever try to work with numbers that are representable in a few decimal digits, you can use that additional information to retrieve the list data. But that assumption clearly doesn't make sense generally, and if you treat all float values that way, you'll be adding more error on average.

That is the reason for this:

from decimal import Decimal Decimal(.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') <====== this is not a rounding problem

Yes it is, it's a rounding problem converting the ".1" literal to a float. There is no exact decimal->float->decimal conversion, which is why the right way to solve this is to avoid the float conversion--e.g., with your decimal literal syntax, or just constructing decimals from stings.

12:39 a.m.

On Friday, March 7, 2014 3:24:06 PM UTC-6, Andrew Barnert wrote:

The decision was discussed at the time, and all the pros and cons were hashed out. If you're not willing to read that discussion, your opinion that the change was a mistake is worth exactly as much as that of any individual user who asked for the change.

hi Andrew, I am satisfied that the problem is well understood and that we no longer need to discuss the root cause, nor the computer science of trying to represent numbers in memory that are not representable. I admire you guys too much to keep going with the previous discussion. I am completely satisfied with Guido's last answer (thank you, by the way), and I look forward to seeing how the community will address the main issue.

In response to your statement quoted above, I would like to suggest seeing if there might be any way you could think of wording that, in future dialogue?

The response quoted above is not only another *ad hominem* attack, it is
also a
straw man attack. Your presumption that I am unwilling to read the
discussion
archive is unfounded and faulty thinking. In fact, I began a thorough
attempt to
understand the previous discussion as soon as I discovered the need through
Steven's revelation last evening. In fact, I am very interested in the
discussion
which led to the decision in question. I not only find it interesting
philosophically
I also am interested in who was involved and how they were thinking at the
time. Of course none of that changes my opinion at this time that the
decision
was not correct. I don't fully know yet, but I think it was made the way
many
decisions are made by young men; like lightning, ---shortest path to
ground.

It would be helpful going forward in future discussions with me that you presume not to put words in my mouth, nor to presume to know how I think or feel, nor presume to attack my character or my person. I would really appreciate it.

Kind regards, marcus

12:42 a.m.

On 7 March 2014 19:20, Guido van Rossum guido@python.org wrote: >

Maybe we can fix the conversion between Decimal and float (if this is really all that matters to Mark, as it appears to be from his last email -- I'd already written most of the above before it arrived). Could it be as simple as converting the float to a string using repr()? Given the smarts in the float repr() that should fix the examples Mark complained about. Are there any roadblocks in the implementation or assumptions of the Decimal type here?

The current Decimal constructor comes with a guarantee of exactness. It accepts int, str and float (and tuple) and creates a Decimal object having the exact value of the int or float or the exact value that results from a decimal interpretation of the str. This exactness is not mandated by any of the standards on which the decimal module is based but I think it is a good thing. I strongly oppose a change that would have it use heuristics for interpreting other numeric types.

Perhaps the default Decimal context being set for a much higher precision makes it philosophically unacceptable?

The context is ignored by the Decimal constructor which will always create an exact value. If you want the created value rounded to context use unary +:

from decimal import Decimal as D D(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') +D(0.1) Decimal('0.1000000000000000055511151231')

Oscar

1:10 a.m.

On Fri, Mar 7, 2014 at 3:42 PM, Oscar Benjamin

oscar.j.benjamin@gmail.comwrote:

On 7 March 2014 19:20, Guido van Rossum guido@python.org wrote: >

Maybe we can fix the conversion between Decimal and float (if this is really all that matters to Mark, as it appears to be from his last email -- I'd already written most of the above before it arrived). Could it be as simple as converting the float to a string using repr()? Given the smarts in the float repr() that should fix the examples Mark complained about. Are there any roadblocks in the implementation or assumptions of the Decimal type here?

The current Decimal constructor comes with a guarantee of exactness.

But Decimal(<float>) is relatively new (it was an error in 2.6). I know
it's annoying to change it again, but I also have the feeling that there's
not much use in producing a Decimal with 53 (or more?) *decimal digits* of
precision from a float with 53 *bits* -- at least not by default. Maybe we
can relent on this guarantee and make the constructor by default use fewer
digits (e.g. using the same algorithm used by repr(<float>)) and have an
optional argument to produce the full precision? Or reserve
Decimal.from_float() for that?

It accepts int, str and float (and tuple) and creates a Decimal object having the exact value of the int or float or the exact value that results from a decimal interpretation of the str. This exactness is not mandated by any of the standards on which the decimal module is based but I think it is a good thing. I strongly oppose a change that would have it use heuristics for interpreting other numeric types.

Well, given that most floating point values are the result of operations
that inherently rounded to 53 bits anyway, I'm not sure that this guarantee
is useful. Sure, I agree that there should be *a* way to construct the
exact Decimal for any IEEE 754 double (since it is always possible, 10
being a multiple of 2), but I'm not sure how important it is that this is
the default constructor.

Perhaps the default Decimal context being set for a much higher precision makes it philosophically unacceptable?

The context is ignored by the Decimal constructor which will always create an exact value. If you want the created value rounded to context use unary +:

from decimal import Decimal as D D(0.1) Decimal('0.1000000000000000055511151231257827021181583404541015625') +D(0.1) Decimal('0.1000000000000000055511151231')

I guess that's too late.

-- --Guido van Rossum (python.org/~guido)

1:27 a.m.

On 8 March 2014 00:10, Guido van Rossum guido@python.org wrote:

On Fri, Mar 7, 2014 at 3:42 PM, Oscar Benjamin oscar.j.benjamin@gmail.com wrote: >

The current Decimal constructor comes with a guarantee of exactness.

But Decimal(<float>) is relatively new (it was an error in 2.6). I know
it's
annoying to change it again, but I also have the feeling that there's not
much use in producing a Decimal with 53 (or more?) *decimal digits* of
precision from a float with 53 *bits* -- at least not by default. Maybe we
can relent on this guarantee and make the constructor by default use fewer
digits (e.g. using the same algorithm used by repr(<float>)) and have an
optional argument to produce the full precision? Or reserve
Decimal.from_float() for that?

I'd rather a TypeError than an inexact conversion. The current behaviour allows to control how rounding occurs. (I don't use the decimal module unless this kind of thing is important).

It accepts int, str and float (and tuple) and creates a Decimal object having the exact value of the int or float or the exact value that results from a decimal interpretation of the str. This exactness is not mandated by any of the standards on which the decimal module is based but I think it is a good thing. I strongly oppose a change that would have it use heuristics for interpreting other numeric types.

Well, given that most floating point values are the result of operations
that inherently rounded to 53 bits anyway, I'm not sure that this guarantee
is useful. Sure, I agree that there should be *a* way to construct the exact
Decimal for any IEEE 754 double (since it is always possible, 10 being a
multiple of 2), but I'm not sure how important it is that this is the
default constructor.

It's hard to reason about the accuracy of calculations that use the decimal module. It's harder than with a fixed-width type because of the possible presence of higher-than-context precision Decimals. The invariant that the constructor is exact (or a TypeError) is of significant benefit when using Decimal for code that accepts inputs of different numeric types.

Oscar

2:02 a.m.

On Fri, Mar 7, 2014 at 4:27 PM, Oscar Benjamin

oscar.j.benjamin@gmail.comwrote:

On 8 March 2014 00:10, Guido van Rossum guido@python.org wrote:

On Fri, Mar 7, 2014 at 3:42 PM, Oscar Benjamin oscar.j.benjamin@gmail.com wrote: >

The current Decimal constructor comes with a guarantee of exactness.

But Decimal(<float>) is relatively new (it was an error in 2.6). I know
it's
annoying to change it again, but I also have the feeling that there's not
much use in producing a Decimal with 53 (or more?) *decimal digits* of
precision from a float with 53 *bits* -- at least not by default. Maybe
we
can relent on this guarantee and make the constructor by default use
fewer
digits (e.g. using the same algorithm used by repr(<float>)) and have an
optional argument to produce the full precision? Or reserve
Decimal.from_float() for that?

I'd rather a TypeError than an inexact conversion. The current behaviour allows to control how rounding occurs. (I don't use the decimal module unless this kind of thing is important).

My proposal still gives you control (e.g. through a keyword argument, or by using Decimal.from_float() instead of the constructor). It just changes the default conversion.

I have to admit I don't recall the discussion that led to accepting float arguments to Decimal(), so I don't know if the exactness guarantee was ever challenged or even discussed. For all other argument types (str, tuple of digits, Decimal) it makes intuitive sense to guarantee exactness, because the input is already in decimal form, so why would the caller pass in digits they don't care about. But for a float the caller has no such control -- I can call round(math.pi, 2) but the resulting float (taken at face value) is still equal to 3.140000000000000124344978758017532527446746826171875, even though just '3.14' is enough as input to float() to produce that exact value.

And unfortunately the default precision for Decimal is much larger than for IEEE double, so using unary + does not get rid of those extraneous digits -- it produces Decimal('3.140000000000000124344978758'). So you really have to work hard to recover produce the intended Decimal('3.14') (or Decimal('3.140000000000000')).

It accepts int, str and float (and tuple) and creates a Decimal object

having the exact value of the int or float or the exact value that results from a decimal interpretation of the str. This exactness is not mandated by any of the standards on which the decimal module is based but I think it is a good thing. I strongly oppose a change that would have it use heuristics for interpreting other numeric types.

Well, given that most floating point values are the result of operations
that inherently rounded to 53 bits anyway, I'm not sure that this
guarantee
is useful. Sure, I agree that there should be *a* way to construct the
exact
Decimal for any IEEE 754 double (since it is always possible, 10 being a
multiple of 2), but I'm not sure how important it is that this is the
default constructor.

It's hard to reason about the accuracy of calculations that use the decimal module. It's harder than with a fixed-width type because of the possible presence of higher-than-context precision Decimals. The invariant that the constructor is exact (or a TypeError) is of significant benefit when using Decimal for code that accepts inputs of different numeric types.

Can you provide an example of this benefit? I can't seem to guess whether you are talking about passing Decimal instances into code that then just uses +, * etc., or whether you are talking about code that uses Decimal internally and takes arguments of different types (e.g. int, float, string).

-- --Guido van Rossum (python.org/~guido)

2:05 a.m.

On Fri, Mar 07, 2014 at 11:01:01AM -0800, Mark H. Harris wrote:

Here is the main post for today, from me, having slept on this and putting some things back into historical timeframe. Sometime before Py2.7.x it was not possible to promote a binary float to a decimal float; the system threw an exception (convert to string).

First, that was the correct way to handle this issue from the inception of decimal.Decimal. Throw an exception and let everyone know that there is a problem; then let the user figure out how to solve it.

No, it was the *conservative* way to handle the issue. By disallowing
the feature, it was possible to add it later without breaking backwards
compatibility. If it had been allowed from the beginning, it would have
been impossible to remove the feature if it had turned out to be a
mistake.

That's incorrect. The way Python converts between the two is the right way to do the conversion. Given a decimal d and a float f constructed from d, f is the closest possible float to d. And the same applies for conversions the other way around.

(This may only apply on platforms with IEEE-754 semantics, but I think that's nearly all platforms CPython runs on these days.)

Second, that was a bad decision--really--IMHO. It was the wrong decision for two reasons 1) it denies what is inherently poor about binary floating point representation in the first place,

What is inherently poor about binary floats is also poor for Decimal floats: limited precision, rounding errors, and violation of fundamental laws of the real number system. These problems, save one, apply equally to Decimal, just in different places with different numbers.

The *sole* difference is that with Decimal there is no rounding between
what the user writes as a decimal string "1.23456" and what the value
generated is. (Decimal also includes a bunch of extra features, like
control of the precision, which floats don't support, but they *could*
be supported if there was enough interest to justify the work.)

This benefit is mostly of use if you are dealing with (1) numerically naive users or (2) using Python as an interactive calculator.

In the case of (1), numerically naive users are not the most important
subset of users for Python. In fact, I would say that numerically
sophisticated users (scientists, mostly) are far more important to the
Python ecosystem, and they *do not want your suggested change*. If we
forced Decimals on them, they would be around with flaming torches and
pitchforks. Seriously, the numeric community would likely abandon Python
(with much wailing and gnashing of teeth) if we forced this on them.

In the case of (2), anyone who has used a common pocket calculator is used to seeing values calculated with a string of 9s or 0s at the end:

2.01000000000001 2.00999999999999

instead of the expected 2.01. In fact, even on advanced scientific
calculators which use decimal floats internally, such as the TI-89, we
still *frequently* witness this problem. It seems to me that the average
numerically naive user has simply learned that "calculators and
computers do approximate calculations" (which is actually correct, when
compared to the mathematically exact result), and don't particularly
care too much about it.

But if *you* care, that's okay. Python is a programming language. You
can *program* it to make an interactive calculator with whatever
properties you desire, including using Decimal floats exclusively.
Python's done 97% of the work for you: the Decimal module, and the cmd
module which makes building interactive command-line oriented
applications easy. You can even simulate the Python interactive
interpreter.

In other words, if you don't want to convert from floats to Decimal,
there is *absolutely no reason* why you should.

and 2) further compounds the problem by denying what is the benefit of using Decimal floating point in the first place.

I must admit I'm not clear as to what you think is the benefit of Decimal which is being denied.

Not to mention, it gives the wrong answer; from a decimal floating point perspective it gives a VERY wrong answer.

Now that's simply not true. The relative error between the "right" answer:

py> from decimal import Decimal as D py> a = D("2.01").sqrt()**2

and the "wrong" answer:

py> b = D(2.01).sqrt()**2

is *miniscule*, just one part in ten thousand million million:

py> (a-b)/a Decimal('1.060511545905472636815920399E-16')

[...]

What needs to happen is that binary floats need to be "correctly" promoted to decimal floats as appropriate. This must not happen by simple copying (that does not work).

If I have given you the impression that float to Decimal conversion occurs by copying the bits from one into the other, I apologise, that was not my intention. That is not what happens.

What actually happens is that given any float, not just floats that are
typed in by hand by the user, are converted to the closest possible
Decimal. What you appear to want is for Python to inspect the value of a
float like 2.0099999999999997868 (the actual float you get from typing
2.01) and intelligently decide that what you *actually* wanted is the
decimal 2.01.

Even if this intelligence was possible, it has one serious flaw that cripples the whole exercise. It cannot apply only to numbers typed in by the user, but would have to apply across the board. Python cannot distinguish between these two cases:

```
Decimal(1.8703152) # Give me this precise decimal
```

and

```
x = 1519.63802016624 # result of some intermediate calculation
y = 812.5037 # result of another intermediate calculation
z = x/y # yet another intermediate calculation
Decimal(z) # convert from float to nearest decimal
```

But z is the binary float 1.8703152, and Decimal cannot do one thing in the first case and a different thing in the second, because it cannot see where the number came from, only what it is. It does not and can not see the literal string, only the float.

In the first case, you want 1.8703152 because that's what the user
typed, but in the second case, we *must* return the result:

```
Decimal('1.8703152000000000665380639475188218057155609130859375')
```

because that's the closest conversion, the one with the least error. Rounding it to 7 decimal places may be sufficient for some applications, but that's not Python's decision to make, any more than Python can decide that if you type 1.99999998 that you must have meant exactly 2.

There needs to be some policy in place that will correctly "set" the decimal float value based on intelligence

http://www.catb.org/jargon/html/D/DWIM.html

-- Steven

2:36 a.m.

On Fri, Mar 07, 2014 at 05:02:15PM -0800, Guido van Rossum wrote:

On Fri, Mar 7, 2014 at 4:27 PM, Oscar Benjamin

oscar.j.benjamin@gmail.comwrote:

On 8 March 2014 00:10, Guido van Rossum guido@python.org wrote: [...]

I also have the feeling that there's not
much use in producing a Decimal with 53 (or more?) *decimal digits* of
precision from a float with 53 *bits* -- at least not by default. Maybe
we can relent on this guarantee and make the constructor by default use
fewer digits (e.g. using the same algorithm used by repr(<float>)) and
have an optional argument to produce the full precision? Or reserve
Decimal.from_float() for that?

I'd rather a TypeError than an inexact conversion. The current behaviour allows to control how rounding occurs. (I don't use the decimal module unless this kind of thing is important).

Like Oscar, I would not like to see this change.

My proposal still gives you control (e.g. through a keyword argument, or by using Decimal.from_float() instead of the constructor). It just changes the default conversion.

Who is this default conversion meant to benefit?

I think that anyone passing floats to Decimal ought to know what they are doing, and be quite capable of controlling the rounding via the decimal context, or via an intermediate string:

value = Decimal("%.3f" % some_float)

I believe that Mark wants to use Python as an interactive calculator, and wants decimal-by-default semantics without having to type quotes around his decimal numbers. I suspect that for the amount of time and effort put into this discussion, he probably could have written an interactive calculator using Decimals with the cmd module in half the time :-)

It seems to me that Decimal should not try to guess what precision the user wants for any particular number. Mark is fond of the example of 2.01, and wants to see precisely 2.01, but that assumes that some human being typed in those three digits. If it's the result of some intermediate calculation, there is no reason to believe that a decimal with the value 2.01 exactly is better than one with the value 2.00999999999999978...

Note that the issue is more than just Decimal:

py> from fractions import Fraction py> Fraction(2.01) Fraction(1131529406376837, 562949953421312)

Even more so than for Decimal, I will go to the barricades to defend this exact conversion. Although perhaps Fraction could grow an extra argument to limit the denominator as a short-cut for this:

py> Fraction(2.01).limit_denominator(10000) Fraction(201, 100)

I think it is important that Decimal and Fraction perform conversions as exactly as possible by default. You can always throw precision away afterwards, but you can't recover what was never saved in the first place.

[...]

And unfortunately the default precision for Decimal is much larger than for IEEE double, so using unary + does not get rid of those extraneous digits -- it produces Decimal('3.140000000000000124344978758'). So you really have to work hard to recover produce the intended Decimal('3.14') (or Decimal('3.140000000000000')).

I wouldn't object to Decimal gaining a keyword argument for precision, although it is easy to use an intermediate string. In other words,

```
Decimal(math.pi, prec=2)
```

could be the same as

```
Decimal("%.2f" % math.pi)
```

But what would prec do for non-float arguments? Perhaps we should leave Decimal() alone and put any extra bells and whistles that apply only to floats into the from_float() method.

-- Steven

3:02 a.m.

On Fri, Mar 7, 2014 at 5:05 PM, Steven D'Aprano steve@pearwood.info wrote:

[...] The way Python converts between the two is the right way to do the conversion.

It's *exact*. I don't know we all agree it is the *right* way.

Given a decimal d and a float f constructed from d, f is the closest possible float to d. And the same applies for conversions the other way around.

It's actually stronger the other way around: when d is constricted from f,
d is *equal* to the mathematical value of f.

The issue (as I see it) is that there are many different decimals d that all convert to the same float f (because of rounding). The d that is constructed by taking the exact value of f is gross overkill. If d was constructed from f by always rounding to (I think) 17 digits it would still back convert exactly to f; the new float repr() gives us an even "better" (i.e. shorter) string of digits that are still guaranteed to to be converted into exactly f (because there's simply no other float that is closer to d than f).

(This may only apply on platforms with IEEE-754 semantics, but I think

that's nearly all platforms CPython runs on these days.)

Sure.

Second, that was a bad decision--really--IMHO. It was the wrong decision for two reasons 1) it denies what is inherently poor about binary floating point representation in the first place,

What is inherently poor about binary floats is also poor for Decimal floats: limited precision, rounding errors, and violation of fundamental laws of the real number system. These problems, save one, apply equally to Decimal, just in different places with different numbers.

The *sole* difference is that with Decimal there is no rounding between
what the user writes as a decimal string "1.23456" and what the value
generated is. (Decimal also includes a bunch of extra features, like
control of the precision, which floats don't support, but they *could*
be supported if there was enough interest to justify the work.)

This benefit is mostly of use if you are dealing with (1) numerically naive users or (2) using Python as an interactive calculator.

In the case of (1), numerically naive users are not the most important
subset of users for Python. In fact, I would say that numerically
sophisticated users (scientists, mostly) are far more important to the
Python ecosystem, and they *do not want your suggested change*.

What is the "suggested change" here? If it's "default float literals to Decimal" I agree. But if the suggestion is my "Decimal(<float>) should be implemented as Decimal(repr(<float>))" I don't think most scientists care (few of them use Decimal, they stay entirely in the realm of binary floating point).

If we forced Decimals on them, they would be around with flaming torches and pitchforks. Seriously, the numeric community would likely abandon Python (with much wailing and gnashing of teeth) if we forced this on them.

Right. In Mark's "post of the day" he already accepts that "decimal by default" is untenable -- and my claim is that even if it was desirable, it would be too big an undertaking. So it's dead.

In the case of (2), anyone who has used a common pocket calculator is used to seeing values calculated with a string of 9s or 0s at the end:

2.01000000000001 2.00999999999999

instead of the expected 2.01.

But the cause is calculations, like 1/3 ==> 0.333333333 and then multiply by 3 ==> 0.999999999. If you enter 2.1, a pocket calculater displays 2.1.

In fact, even on advanced scientific
calculators which use decimal floats internally, such as the TI-89, we
still *frequently* witness this problem.

I suspect that if Python exhibited the problem in exactly those cases where
a TI-89 exhibits it, few people would complain. The complaints would most
likely come from numerically *sophisticated* users who object against using
decimal (since binary has much better rounding behavior for arithmetic
operations).

It seems to me that the average numerically naive user has simply learned that "calculators and computers do approximate calculations" (which is actually correct, when compared to the mathematically exact result), and don't particularly care too much about it.

And yet I have seen plenty of Tweets, emails and other public discussions where people gleefully pointed out that "Python has a bug." I think I've also seen tracker items created for such issues.

But if *you* care, that's okay. Python is a
programming language. You
can *program* it to make an interactive calculator with whatever
properties you desire, including using Decimal floats exclusively.

That sounds a bit condescending. :-(

Python's done 97% of the work for you: the Decimal module, and the cmd

module which makes building interactive command-line oriented applications easy. You can even simulate the Python interactive interpreter.

In other words, if you don't want to convert from floats to Decimal,
there is *absolutely no reason* why you should.

and 2) further compounds the problem by denying what is the benefit of using Decimal floating point in the first place.

I must admit I'm not clear as to what you think is the benefit of Decimal which is being denied.

If I may venture a guess, I think the assumed benefit of Decimal here is that it doesn't shatter naive users' expectations.

Not to mention, it gives the wrong answer; from a decimal floating point perspective it gives a VERY wrong answer.

Now that's simply not true. The relative error between the "right" answer:

py> from decimal import Decimal as D py> a = D("2.01").sqrt()**2

and the "wrong" answer:

py> b = D(2.01).sqrt()**2

is *miniscule*, just one part in ten thousand million million:

py> (a-b)/a Decimal('1.060511545905472636815920399E-16')

This feels like unnecessary pedantry. (Not the first time in this thread. :-( )

[...]

What needs to happen is that binary floats need to be "correctly" promoted to decimal floats as appropriate. This must not happen by simple copying (that does not work).

If I have given you the impression that float to Decimal conversion occurs by copying the bits from one into the other, I apologise, that was not my intention. That is not what happens.

What actually happens is that given any float, not just floats that are typed in by hand by the user, are converted to the closest possible Decimal.

Well, actually, a Decimal that is mathematically *equal*. (I understand
that this is technically the "closest possible" but the use of the latter
phrase suggests that it's not always *equal*, which the actual algorithm
used always produces a value that is mathematically equal to the input
float.)

What you appear to want is for Python to inspect the
value of a
float like 2.0099999999999997868 (the actual float you get from typing
2.01) and intelligently decide that what you *actually* wanted is the
decimal 2.01.

Even if this intelligence was possible, it has one serious flaw that cripples the whole exercise. It cannot apply only to numbers typed in by the user, but would have to apply across the board. Python cannot distinguish between these two cases:

```
Decimal(1.8703152) # Give me this precise decimal
```

and

```
x = 1519.63802016624 # result of some intermediate calculation
y = 812.5037 # result of another intermediate calculation
z = x/y # yet another intermediate calculation
Decimal(z) # convert from float to nearest decimal
```

But z is the binary float 1.8703152, and Decimal cannot do one thing in the first case and a different thing in the second, because it cannot see where the number came from, only what it is. It does not and can not see the literal string, only the float.

In the first case, you want 1.8703152 because that's what the user
typed, but in the second case, we *must* return the result:

```
Decimal('1.8703152000000000665380639475188218057155609130859375')
```

because that's the closest conversion, the one with the least error.

Steven, that's actually not true. print(z) produces 1.8703152, and if I enter float(Decimal('1.8703152')) I get the exact same value of z. Try it. (In Python 2.7 or 3, please.) So I claim that if Decimal(z) produced the same value as Decimal('1.8703152') nothing would be lost.

Rounding it to 7 decimal places may be sufficient for some applications, but that's not Python's decision to make, any more than Python can decide that if you type 1.99999998 that you must have meant exactly 2.

The repr() function does not round to a fixed number of decimals. It
produces (in theory, dynamically, although I suspect that the current
algorithm is better) the shortest decimal string that, when converted back
to binary, equals *exactly* the input.

>

There needs to be some policy in place that will correctly "set" the decimal float value based on intelligence

Also condescending. :-(

-- --Guido van Rossum (python.org/~guido)

3:10 a.m.

On Sat, Mar 8, 2014 at 1:02 PM, Guido van Rossum guido@python.org wrote:

The repr() function does not round to a fixed number
of decimals. It
produces (in theory, dynamically, although I suspect that the current
algorithm is better) the shortest decimal string that, when converted back
to binary, equals *exactly* the input.

If that's the definition of a float's repr, then I'd support using that by default for the construction of a Decimal from a float. You can always use .as_integer_ratio() to get the exact stored representation.

ChrisA

3:15 a.m.

On Fri, Mar 7, 2014 at 5:36 PM, Steven D'Aprano steve@pearwood.info wrote:

On Fri, Mar 07, 2014 at 05:02:15PM -0800, Guido van Rossum wrote:

On Fri, Mar 7, 2014 at 4:27 PM, Oscar Benjamin

oscar.j.benjamin@gmail.comwrote:

On 8 March 2014 00:10, Guido van Rossum guido@python.org wrote: [...]

I also have the feeling that there's not
much use in producing a Decimal with 53 (or more?) *decimal digits*
of
precision from a float with 53 *bits* -- at least not by default.
Maybe
we can relent on this guarantee and make the constructor by default
use
fewer digits (e.g. using the same algorithm used by repr(<float>))
and
have an optional argument to produce the full precision? Or reserve
Decimal.from_float() for that?

Like Oscar, I would not like to see this change.

Can you argue your position?

My proposal still gives you control (e.g. through a keyword argument, or by using Decimal.from_float() instead of the constructor). It just changes the default conversion.

Who is this default conversion meant to benefit?

I think that anyone passing floats to Decimal ought to know what they are doing,

I think that is provably not the case. (That they don't know -- we can still argue about whether they ought to know.)

and be quite capable of controlling the rounding via the decimal context, or via an intermediate string:

value = Decimal("%.3f" % some_float)

Or, Decimal(repr(some_float)), which DWIM.

I believe that Mark wants to use Python as an interactive calculator, and wants decimal-by-default semantics without having to type quotes around his decimal numbers. I suspect that for the amount of time and effort put into this discussion, he probably could have written an interactive calculator using Decimals with the cmd module in half the time :-)

Mark doesn't like it when we try to guess what he means or believes, but my guess is that he's actually trying to argue on behalf of Python users who would benefit from using Decimal but don't want to be bothered with a lot of rules. Telling such people "use the Decimal class" is much easier than telling them "use the Decimal class, and always put quotes around your numbers" (plus the latter advice is still very approximate, so the real version would be even longer and more confusing).

It seems to me that Decimal should not try to guess what precision the user wants for any particular number. Mark is fond of the example of 2.01, and wants to see precisely 2.01, but that assumes that some human being typed in those three digits. If it's the result of some intermediate calculation, there is no reason to believe that a decimal with the value 2.01 exactly is better than one with the value 2.00999999999999978...

Given the effort that went into making repr(<float>) do the right thing I think that you're dismissing a strawman here. The real proposal does not require guessing intent -- it just uses an existing, superior conversion from float to decimal that is already an integral part of the language. And it uses some static information that Decimal currently completely ignores, i.e. all Python floats (being IEEE doubles) are created with 53 bits of precision.

Note that the issue is more than just Decimal:

py> from fractions import Fraction py> Fraction(2.01) Fraction(1131529406376837, 562949953421312)

Even more so than for Decimal, I will go to the barricades to defend this exact conversion. Although perhaps Fraction could grow an extra argument to limit the denominator as a short-cut for this:

py> Fraction(2.01).limit_denominator(10000) Fraction(201, 100)

An inferior solution. Anyway, nobody cares about Fraction.

I think it is important that Decimal and Fraction perform conversions as exactly as possible by default.

Again, why produce 56 *digits* of precision by default for something that
only intrinsically has 53 *bits*?

You can always throw precision away afterwards, but you can't recover what was never saved in the first place.

There should definitely be *some* API to convert a float to the exact
mathematical Decimal. But I don't think it necessarily needs to be the
default constructor. (It would be different if Decimal was a fixed-point
type. But it isn't.)

[...]

And unfortunately the default precision for Decimal is much larger than for IEEE double, so using unary + does not get rid of those extraneous digits -- it produces Decimal('3.140000000000000124344978758'). So you really have to work hard to recover produce the intended Decimal('3.14') (or Decimal('3.140000000000000')).

I wouldn't object to Decimal gaining a keyword argument for precision, although it is easy to use an intermediate string. In other words,

```
Decimal(math.pi, prec=2)
```

could be the same as

```
Decimal("%.2f" % math.pi)
```

This is actually another strawman. Nobody in this thread has asked for a
way to truncate significant digits like that. It's the *insignificant*
digits that bother some. The question is, what should Decimal(math.pi) do.
It currently returns
Decimal('3.141592653589793115997963468544185161590576171875'), which helps
noone except people interested in how floats are represented internally.

But what would prec do for non-float arguments? Perhaps we should leave Decimal() alone and put any extra bells and whistles that apply only to floats into the from_float() method.

-- --Guido van Rossum (python.org/~guido)

3:19 a.m.

On Fri, Mar 7, 2014 at 6:10 PM, Chris Angelico rosuav@gmail.com wrote:

On Sat, Mar 8, 2014 at 1:02 PM, Guido van Rossum guido@python.org wrote:

The repr() function does not round to a fixed
number of decimals. It
produces (in theory, dynamically, although I suspect that the current
algorithm is better) the shortest decimal string that, when converted
back
to binary, equals *exactly* the input.

If that's the definition of a float's repr, then I'd support using that by default for the construction of a Decimal from a float. You can always use .as_integer_ratio() to get the exact stored representation.

Sadly I can't point to exactly where this is documented, but since Python 2.7 and some early version of 3.x that is indeed how repr(<float>) works.

(And str(<float>) too -- before 2.7, str() would give only 12 digits while repr() gave 17, but the new repr() makes such shenanigans unnecessary. So I could have saved myself a few characters by using str() instead of repr() everywhere in this thread. :-)

-- --Guido van Rossum (python.org/~guido)

3:46 a.m.

On Sat, Mar 8, 2014 at 1:19 PM, Guido van Rossum guido@python.org wrote:

On Fri, Mar 7, 2014 at 6:10 PM, Chris Angelico rosuav@gmail.com wrote: >

On Sat, Mar 8, 2014 at 1:02 PM, Guido van Rossum guido@python.org wrote:

*exactly* the input.

If that's the definition of a float's repr, then I'd support using that by default for the construction of a Decimal from a float. You can always use .as_integer_ratio() to get the exact stored representation.

Sadly I can't point to exactly where this is documented, but since Python 2.7 and some early version of 3.x that is indeed how repr(<float>) works.

I couldn't find it anywhere - help(float.__repr__) just points to it being the implementation of repr(some_float), and poking around on the docs didn't show up any explanation. It'd be something worth mentioning somewhere, as that's a great feature, but I don't really know where, heh.

ChrisA

3:50 a.m.

On 3/7/2014 9:19 PM, Guido van Rossum wrote:

On Fri, Mar 7, 2014 at 6:10 PM, Chris Angelico <rosuav@gmail.com

mailto:rosuav@gmail.com> wrote: If that's the definition of a float's repr, then I'd support using that by default for the construction of a Decimal from a float. You can always use .as_integer_ratio() to get the exact stored representation.

Sadly I can't point to exactly where this is documented, but since Python 2.7 and some early version of 3.x that is indeed how repr(<float>) works.

It's mentioned at the bottom of this page: http://docs.python.org/2/tutorial/floatingpoint.html

But oddly the wording in http://docs.python.org/3/tutorial/floatingpoint.html is not as precise. There are some weasel words in there because we do have a fallback if we can't run Gay's code on some systems (sys.float_repr_style='legacy'). But I think for all practical purposes it's always true.

(And str(<float>) too -- before 2.7, str() would give only 12 digits while repr() gave 17, but the new repr() makes such shenanigans unnecessary. So I could have saved myself a few characters by using str() instead of repr() everywhere in this thread. :-)

Indeed!

Eric.

3:51 a.m.

In article

CAP7+vJKFYy+RGbryMSWzXaExZajL1zUTmiWPdnPeG5RpkmRHiw@mail.gmail.com, Guido van Rossum guido@python.org wrote:

On Fri, Mar 7, 2014 at 6:10 PM, Chris Angelico rosuav@gmail.com wrote:

If that's the definition of a float's repr, then I'd support using that by default for the construction of a Decimal from a float. You can always use .as_integer_ratio() to get the exact stored representation. Sadly I can't point to exactly where this is documented, but since Python 2.7 and some early version of 3.x that is indeed how repr(<float>) works.

If nowhere else:

http://docs.python.org/3/whatsnew/3.1.html#other-language-changes http://bugs.python.org/issue1580

http://docs.python.org/2/whatsnew/2.7.html#other-language-changes http://bugs.python.org/issue7117

-- Ned Deily, nad@acm.org

4:24 a.m.

On Fri, Mar 7, 2014, at 16:24, Andrew Barnert wrote:

Actually, arbitrary-precision decimal floats are a superset of binary floats, because 2 is a factor of 10. As the binary floats get smaller you need more and more digits to represent the decimal though.

decimal.Decimal(1/2**1074) Decimal('4.940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625E-324')

4:46 a.m.

random832@fastmail.us writes:

Or, for that matter, you could limit both the numerator and the denominator to some maximum value - when an intermediate result exceeds it (won't be more than the square of the maximum for most operations), you find the closest representable value.

Knuth calls that "fixed slash" (actually somewhat more general than I think you mean -- numerator and denominator can have different maximum values).

4:55 a.m.

On Fri, Mar 7, 2014 at 6:15 PM, Guido van Rossum guido@python.org wrote:

Or, Decimal(repr(some_float)), which DWIM.

Because I haven't seen anyone else bring this up, using a non-exact conversion breaks the invariant "Decimal(f)==f".

There are so many pairs of numeric types that break that invariant that it might not be a big deal; but in all other cases the invariant is broken because it is theoretically impossible.

from decimal import Decimal f = 2.2 print(Decimal(f)==f) print(Decimal(repr(f))==f)

p

4:59 a.m.

On Friday, March 7, 2014 8:15:11 PM UTC-6, Guido van Rossum wrote: > >

. . . but my guess is that he's actually trying to argue on behalf of Python users who would benefit from using Decimal but don't want to be bothered with a lot of rules. Telling such people "use the Decimal class" is much easier than telling them "use the Decimal class, and always put quotes around your numbers" (plus the latter advice is still very approximate, so the real version would be even longer and more confusing).

Yes, that is correct. Although, I would modify "with certain rules". I mean, its ok to expect some rules (I think that's what we mean by semantic). But rules that seem to go against the grain of the normal human experience ... like quoting a numerical input. I mean if you tell me as a programmer that's what I have to do, well that's what I have to do. On the other hand (the hand I'm really concerned about) if you tell a naive user they have to quote their numerical inputs-- that seems like the wrong approach. Yes, its much better to say, "Please use the Decimal module".

marcus

5 a.m.

On Sat, Mar 8, 2014 at 2:55 PM, Paul Du Bois paul.dubois@gmail.com wrote:

On Fri, Mar 7, 2014 at 6:15 PM, Guido van Rossum guido@python.org wrote: >

Or, Decimal(repr(some_float)), which DWIM.

Because I haven't seen anyone else bring this up, using a non-exact conversion breaks the invariant "Decimal(f)==f".

There are so many pairs of numeric types that break that invariant that it might not be a big deal; but in all other cases the invariant is broken because it is theoretically impossible.

from decimal import Decimal f = 2.2 print(Decimal(f)==f) print(Decimal(repr(f))==f)

How is Decimal==float implemented? Is it by calling Decimal(rhs) and then comparing? If so, changing how Decimal(float) works won't break the invariant, as it'll make the same conversion each time.

ChrisA

5:11 a.m.

On Fri, Mar 7, 2014 at 8:00 PM, Chris Angelico rosuav@gmail.com wrote:

On Sat, Mar 8, 2014 at 2:55 PM, Paul Du Bois paul.dubois@gmail.com wrote:

On Fri, Mar 7, 2014 at 6:15 PM, Guido van Rossum guido@python.org wrote: >

Or, Decimal(repr(some_float)), which DWIM.

Because I haven't seen anyone else bring this up, using a non-exact conversion breaks the invariant "Decimal(f)==f".

There are so many pairs of numeric types that break that invariant that it might not be a big deal; but in all other cases the invariant is broken because it is theoretically impossible.

from decimal import Decimal f = 2.2 print(Decimal(f)==f) print(Decimal(repr(f))==f)

How is Decimal==float implemented? Is it by calling Decimal(rhs) and then comparing? If so, changing how Decimal(float) works won't break the invariant, as it'll make the same conversion each time.

There's a special function to convert the other operand for the purpose of comparisons, and it currently uses Decimal.from_float(). I am not (yet :-) proposing to change the behavior of that function -- it is and has always been the API function that does exact float-to-Decimal conversion. Decimal(repr(f)) == f returns False today (for the majority of float values anyway) and under my proposal Decimal(f) == f would also return False. I don't (yet :-) think that's a deal breaker.

-- --Guido van Rossum (python.org/~guido)

5:16 a.m.

On Sat, Mar 8, 2014 at 3:11 PM, Guido van Rossum guido@python.org wrote:

How is Decimal==float implemented? Is it by calling Decimal(rhs) and then comparing? If so, changing how Decimal(float) works won't break the invariant, as it'll make the same conversion each time.

There's a special function to convert the other operand for the purpose of comparisons, and it currently uses Decimal.from_float(). I am not (yet :-) proposing to change the behavior of that function -- it is and has always been the API function that does exact float-to-Decimal conversion. Decimal(repr(f)) == f returns False today (for the majority of float values anyway) and under my proposal Decimal(f) == f would also return False. I don't (yet :-) think that's a deal breaker.

Maybe not a deal breaker, but certainly another panel on the bikeshed. It would make good sense to retain that invariant by making the comparison use repr just like the constructor.

ChrisA

5:38 a.m.

On Fri, Mar 7, 2014 at 8:16 PM, Chris Angelico rosuav@gmail.com wrote:

On Sat, Mar 8, 2014 at 3:11 PM, Guido van Rossum guido@python.org wrote:

There's a special function to convert the other operand for the purpose of comparisons, and it currently uses Decimal.from_float(). I am not (yet :-) proposing to change the behavior of that function -- it is and has always been the API function that does exact float-to-Decimal conversion. Decimal(repr(f)) == f returns False today (for the majority of float values anyway) and under my proposal Decimal(f) == f would also return False. I don't (yet :-) think that's a deal breaker.

Maybe not a deal breaker, but certainly another panel on the bikeshed. It would make good sense to retain that invariant by making the comparison use repr just like the constructor.

Not sure I agree. I find the invariant Decimal(x) == x rather uninteresting. I find the idea that Decimal.__eq__ compares the exact mathematical values very appealing.

-- --Guido van Rossum (python.org/~guido)

5:39 a.m.

[Guido]

Decimal(repr(f)) == f returns False today (for the majority of float values anyway) and under my proposal Decimal(f) == f would also return False. I don't (yet :-) think that's a deal breaker.

To the contrary, that's "a feature". Current Pythons take equality testing seriously, striving to return True when & only when values are mathematically ("as if using infinite precision") identical, even when that's inconvenient to do:

2**500 == 2.0**500
True
2**500 + 1 == 2.0**500
False

If information is lost when converting, it's doing nobody a favor for "==" to pretend it hasn't been lost. So long as Decimal.from_float(f) == f is still True, then equality would be working as expected in all cases.

5:40 a.m.

Sometime today I think I wrote that the C implementation of Decimal isn't yet in the stdlib. I stand corrected. It is. Yay!

-- --Guido van Rossum (python.org/~guido)

5:44 a.m.

On Fri, Mar 7, 2014 at 8:39 PM, Tim Peters tim.peters@gmail.com wrote:

[Guido]

Decimal(repr(f)) == f returns False today (for the majority of float values anyway) and under my proposal Decimal(f) == f would also return False. I don't (yet :-) think that's a deal breaker.

To the contrary, that's "a feature". Current Pythons take equality testing seriously, striving to return True when & only when values are mathematically ("as if using infinite precision") identical, even when that's inconvenient to do:

2**500 == 2.0**500
True
2**500 + 1 == 2.0**500
False

If information is lost when converting, it's doing nobody a favor for "==" to pretend it hasn't been lost. So long as Decimal.from_float(f) == f is still True, then equality would be working as expected in all cases.

I agree. But what do you think of my main proposal? I would retract it if you advised me so.

-- --Guido van Rossum (python.org/~guido)

6:01 a.m.

[Guido]

I agree. But what do you think of my main proposal? I would retract it if you advised me so.

Oh, I stick to tiny detail these days, to avoid getting sucked into endless pointless arguments ;-)

That said, I agree that the default Decimal constructor acting like Decimal(x) == Decimal(repr(x)) would be far less surprising to the vast majority of users, and no problem at all for "expert" users (provided Decimal.from_float(x) were still available).

Against it, the change would invalidate dozens of stackoverflow answers advising users to try printing Decimal(x) to see where their naive binary floating-point expectations are going wrong. And I have no feel for how much production code may be relying on current Decimal(x) behavior (I have none).

But, if we're willing to risk changing the meaning of bool(datetime,time(arguments creating an object in some timezones - but not all - that converts to midnight UTC )), then I suppose the floodgates are waaaaaay open now ;-)

6:33 a.m.

On Mar 7, 2014, at 20:44, Guido van Rossum guido@python.org wrote:

I agree. But what do you think of my main proposal? I would retract it if you advised me so.

I think this may be a good time to raise my points from off-list, in hopes that someone can say "those potential problems are not real problems anyone would ever care about".

Today, given any real, Decimal(float('real')) always gives you the value of the closest binary float to that real. With this change, it will sometimes give you the value of the second-closest repr-of-a-binary-float to that real. This means the error across any range of reals increases. It's still below the rule-of-thumb cutoff that everyone uses for converting through floats, but it is higher by a nonzero amount that doesn't cancel out.

Similarly, today, the distribution of float values across the real number line is... not uniform (because of exponents), and I don't know the right technical term, you know what it looks like. The distribution of repr-of-float variables is not.

Do users of Decimal (or, rather, users of Decimal(float) conversion) care about either of those issues? I don't know. I've never written any code that converted floats to Decimals and then used them as anything other than low-precision fixed-point values (like dollars and cents).

By the way, to dismiss any potential performance worries: with both 3.3 and trunk, Decimal(repr(f)) is actually faster than Decimal.from_float(f) by a factor of 1.3 to 3.2 in a variety of quick tests. If that weren't true, you might have to define Decimal(f) as if it were building and then parsing a string but them implement more complicated code that feeds digits from one algorithm to the other, but fortunately, it looks like this is a three-line change.

6:40 a.m.

On Friday, March 7, 2014 3:24:06 PM UTC-6, Andrew Barnert wrote:

The decision was discussed at the time, and all the pros and cons were hashed out. If you're not willing to read that discussion, your opinion that the change was a mistake is worth exactly as much as that of any individual user who asked for the change.

hi Andrew, I have been studying the python-ideas archive & the python-dev archive all night. I have read hundreds of posts. I am finding something very interesting. My proposal has been coming up (time and again) in different flavors for many years; with all the same people participating (with all of the very same discussion almost verbatim).

Just for history sake, I thought you might be interested in a blast from the past from Raymond Hettinger in response to Lennart Benschop who made the decimal literal proposal in Oct, 2007:

Just for historical context only:

On Oct 26, 1:54 am, Lennart Benschop <[hidden email]http://python.6.x6.nabble.com/user/SendEmail.jtp?type=node&node=1580823&...> wrote:

My proposal:

- Any decimal constant suffixed with the letter "D" or "d" will be interpreted as a literal of the Decimal type. This also goes for decimal constants with exponential notation.

There's nothing new here that hasn't already been proposed and discussed on python-dev. There were no major objections to the idea; however, it will need to wait until there is a good C implementation of the decimal module (which is in the works but coming along very, very slowly).

{from the history department}

7:08 a.m.

On Fri, Mar 07, 2014 at 09:40:47PM -0800, Mark H. Harris wrote:

hi Andrew, I have been studying the python-ideas archive & the python-dev archive all night. I have read hundreds of posts. I am finding something very interesting. My proposal has been coming up (time and again) in different flavors for many years; with all the same people participating (with all of the very same discussion almost verbatim).

Which proposal? You have made a few, and I cannot keep track of which ones you still stand by and which ones you have abandoned.

that Python unify all numeric types to one "Python Number" (I think this is abandoned)

that Decimal(float) use AI to determine what number the programmer wanted (you've agreed to stop using the term "AI", but I'm not entirely sure whether you've moved away from this position entirely -- you still describe Python as needing to "intelligently" convert floats)

that allowing Decimal(float) at all is a mistake and should be prohibited

that the default numeric type when the programmer enters a numeral with a decimal point like 2.01 be Decimal

and possibly others. Which proposal are you referring to here?

Just for history sake, I thought you might be interested in a blast from the past from Raymond Hettinger in response to Lennart Benschop who made the decimal literal proposal in Oct, 2007: [snip]

Would you mind providing a URL for that message so we can see the context please?

-- Steven

7:14 a.m.

On 3/7/2014 2:20 PM, Guido van Rossum wrote:

However, for the growing contingent of scientists who use Python as a replacement for Matlab (not Mathematica!), it could be a big nuisance. They don't care about decimal issues (they actually like binary better)

One reason knowledgeable users of floating point numbers as approximations like binary better is that the binary floating point system is much 'smoother' than the decimal floating point system. For example, with 3 decimal digits, consider .999, 1.00, 1.01. The first difference is .001, the second is .01, 10 x larger. This is one of the problems of working with base 10 slide rules. For binary, the largest ratio between differences is 2 rather than 10.

and they write lots of C++ code that interfaces between CPython's internal API and various C++ libraries for numeric data processing, all of which use IEEE binary.

This too ;-).

-- Terry Jan Reedy

7:18 a.m.

From: Steven D'Aprano steve@pearwood.info

Sent: Friday, March 7, 2014 10:08 PM

On Fri, Mar 07, 2014 at 09:40:47PM -0800, Mark H. Harris wrote:

hi Andrew, I have been studying the python-ideas archive & the python-dev archive all night. I have read hundreds of posts. I am finding something very interesting. My proposal has been coming up (time and again) in different flavors for many years; with all the same people participating (with all of the very same discussion almost verbatim).

Which proposal? You have made a few, and I cannot keep track of which ones you still stand by and which ones you have abandoned.

I agree that some clarity would be nice here, but I think from the quote he quoted it's pretty clear that he's talking about the decimal-literal proposal, not any of the ones that you mentioned.

- that Python unify all numeric types to one "Python

Number" (I think this is abandoned)

- that Decimal(float) use AI to determine what number the programmer wanted (you've agreed to stop using the term "AI", but I'm not entirely sure whether you've moved away from this position entirely -- you still describe Python as needing to "intelligently" convert floats)

I'm still not sure exactly what Mark's proposal is here, but Guido has developed this one into something concrete and feasible: change the default conversion from float to Decimal (that is, Decimal(float)) to use the repr. I don't want to put works in Mark's mouth and say that's what he's interested in here, but I can say that it looks like it would solve the problem cases Mark has brought up.

that allowing Decimal(float) at all is a mistake and should be prohibited

that the default numeric type when the programmer enters a numeral with a decimal point like 2.01 be Decimal

7:36 a.m.

On Fri, Mar 07, 2014 at 09:33:38PM -0800, Andrew Barnert wrote:

Today, given any real, Decimal(float('real')) always gives you the value of the closest binary float to that real. With this change, it will sometimes give you the value of the second-closest repr-of-a-binary-float to that real.

Please don't talk about "reals". "Real" has a technical meaning in mathematics, and no computer number system is able to represent the reals. In Python, we have floats, which are C doubles, and Decimals.

Putting that aside, I'm afraid I don't understand how Guido's suggestion to use repr() in the Decimal constructor will sometimes give the second-closest value. Can you explain in more detail? If you can show an example, that would be great too.

This means the error across any range of reals increases. It's still below the rule-of-thumb cutoff that everyone uses for converting through floats, but it is higher by a nonzero amount that doesn't cancel out.

Similarly, today, the distribution of float values across the real number line is... not uniform (because of exponents), and I don't know the right technical term, you know what it looks like. The distribution of repr-of-float variables is not.

Nope, sorry, I don't get you.

I think that what you are trying to say is that for each binary exponent, the floats with that same exponent are distributed uniformly:

|...|...|...|...|...|...|...|...|

but their reprs are not:

|...|..|..|....|...|....|..|....|

Is this what you mean?

-- Steven

7:40 a.m.

On Saturday, March 8, 2014 12:08:30 AM UTC-6, Steven D'Aprano wrote:

Which proposal are you referring to here?

hi Steven, well, all of them. The archive has discussions pertaining to every one of the discussions we have had on this list. From "python has a bug," to python needs a decimal literal, to "how come this Decimal(.01) doesn't work," to (believe this or not) one person on the ideas archive actually proposed that "all python numbers be unified," I kid you not. Guido put that one to bed easily with one post and there was no further discussion of the topic, but the idea is that I'm not the first one to think about these things. The discussions are pretty much the same as the ones we've had here on this list with people frustrated for pretty much the same reasons. ( some of it ignorance, some naiveté, some over-zealous, one or two crazy like me).

Go here: http://python.6.x6.nabble.com/Python-python-dev-f1801473.html

and search for D'Aprano decimal or D'Aprano Decimal

or search on unify python numbers

The discussions are very interesting, very educational, and very entertaining.

marcus

7:47 a.m.

On Saturday, March 8, 2014 12:40:36 AM UTC-6, Mark H. Harris wrote:

Steve, Andrew, here:

http://osdir.com/ml/python.ideas/2008-01/msg00079.html

Mr Toronto was crazy like me...

7:52 a.m.

On Saturday, March 8, 2014 12:18:53 AM UTC-6, Andrew Barnert wrote:

but Guido has developed this one into something concrete and feasible: change the default conversion from float to Decimal (that is, Decimal(float)) to use the repr.

What you and Guido are discussing is perfect. Thank you.

marcus

8:06 a.m.

On Saturday, March 8, 2014 12:08:30 AM UTC-6, Steven D'Aprano wrote:

Which proposal?

hi Steve, the reading I would like to find, and can't, is the discussion that led to the change in behavior in Py2.7; where convert from float stopped giving the (convert to string message).

I am very interested in reading that discussion. Do you have a link to that?

Thanks

marcus

8:08 a.m.

On Fri, Mar 07, 2014 at 06:02:02PM -0800, Guido van Rossum wrote:

On Fri, Mar 7, 2014 at 5:05 PM, Steven D'Aprano steve@pearwood.info wrote:

[...] The way Python converts between the two is the right way to do the conversion.

It's *exact*. I don't know we all agree it is the *right* way.

Fair point. I can't argue with that :-)

Given a decimal d and a float f constructed from d, f is the closest possible float to d. And the same applies for conversions the other way around.

It's actually stronger the other way around: when d is constricted from f,
d is *equal* to the mathematical value of f.

Ah, so it is.

The issue (as I see it) is that there are many different decimals d that all convert to the same float f (because of rounding). The d that is constructed by taking the exact value of f is gross overkill.

Decimal -> float is many-to-one: more than one Decimal will round to a single float. But float -> Decimal is always one-to-one, I think, regardless of whether you use the current exact conversion or repr first. The crux of the matter is whether or not it is overkill for Decimal to use the exact value.

Correct me if I'm wrong, but I think we agree that there ought to be a way to convert floats exactly to Decimal, we just disagree on whether that ought to be spelled Decimal(x) or Decimal.from_float(x).

Likewise I think we agree that there ought to be some way to convert floats to the nearest "simple" Decimal, with the question being whether that ought to be spelled Decimal(repr(x)) or Decimal(x).

If we're all in agreement that this is the only serious area of disagreement for a change which has any hope of appearing in 3.5, then we can put aside the more speculative proposals

-- Steven

8:35 a.m.

On Fri, Mar 07, 2014 at 11:06:47PM -0800, Mark H. Harris wrote:

hi Steve, the reading I would like to find, and can't, is the discussion that led to the change in behavior in Py2.7; where convert from float stopped giving the (convert to string message).

I am very interested in reading that discussion. Do you have a link to that?

http://bugs.python.org/issue8257 https://mail.python.org/pipermail/python-dev/2010-March/098699.html

and in particular:

https://mail.python.org/pipermail/python-dev/2010-March/098717.html

-- Steven

8:50 a.m.

On Saturday, March 8, 2014 12:52:32 AM UTC-6, Mark H. Harris wrote:

but Guido has developed this one into something concrete and feasible:

change the default conversion from float to Decimal (that is, Decimal(float)) to use the repr.

What you and Guido are discussing is perfect. Thank you.

Guido, Andrew, I have a question about repr() in my current codes for 3.3.4 and below:

Would it be reasonable to add a function to pdeclib to be used internally like this:

def Dec(numform): return Decimal(repr(numform))

Does this work correctly (for pdeclib internally) ? I think this works better than my first stab at this (just using str() ) because repr() acts like an eval() ??

Do I understand this correctly?

It looks like what you are proposing will fix the problem I've been whining about. Thank you.

marcus

8:53 a.m.

From: Steven D'Aprano steve@pearwood.info

Sent: Friday, March 7, 2014 10:36 PM

On Fri, Mar 07, 2014 at 09:33:38PM -0800, Andrew Barnert wrote:

Today, given any real, Decimal(float('real')) always gives you the value of the closest binary float to that real. With this change, it will sometimes give you the value of the second-closest repr-of-a-binary-float to that real.

Please don't talk about "reals". "Real" has a technical meaning in mathematics, and no computer number system is able to represent the reals. In Python, we have floats, which are C doubles, and Decimals.

I am using "real" in the technical sense. That's the whole crux of the problem: you want to use the real[1] number 0.1 in Python, and you can't, because there is no IEEE double that matches that value. If we ignore reals and only talk about floats, then 0.1 and 0.100000000000000012 are the same number, so there is no problem.

[1]: I suppose we could talk about rationals instead of reals here, because (a) you can't enter irrationals as literals, and (b) (b) Guido's proposal obviously doesn't attempt to help with them. I don't think that makes a difference here, but I could be wrong; if I am, please point out below where this leads me astray.

Put another way: This cannot be just about converting IEEE double floats to Decimal floats, because Python already does that perfectly. It's about recovering information lost in representing real numbers (or, if you prefer, rational numbers, or Python literals) as IEEE double floats, when converting them to Decimal floats, by taking advantage of the fact that (a) we know that humans tend to use numbers like 0.1 more often than numbers like 0.100000000000000005551, and (b) we know the precision of IEEE double and can guarantee that we're not losing any real information.

Putting that aside, I'm afraid I don't understand how Guido's suggestion to use repr() in the Decimal constructor will sometimes give the second-closest value. Can you explain in more detail? If you can show an example, that would be great too.

OK, take the number 0.100000000000000012.

The closest IEEE double to this number is 0.1000000000000000055511151231257827021181583404541015625. The next closest is 0.10000000000000001942890293094023945741355419158935546875. Today's design gives you the former when you write Decimal(0.100000000000000012). You get the closest one in this case, and in every case.

The closest repr of an IEEE double to this number is 0.10000000000000002. But with Guido's proposal, the one you get is 0.1, which is farther from the number you wanted.

So, if you treat evaluate-literal-as-float-then-Decimal.from_float as a (mathematical) function, it has the property that it always gives you the closest value from its range to your input. If you treat evaluate-literal-as-float-then-repr-then-Decimal as a function, it does not have that property.

This means the error across any

range of reals increases. It's still below the rule-of-thumb cutoff that everyone uses for converting through floats, but it is higher by a nonzero amount that doesn't cancel out.

Was this second point clear? In case it wasn't, let me expand on it:

In the example above, Guido's proposal gives you nearly double the error (1.2e-17 vs. 6.4+e-18) of the current design. Of course there are also cases where it gives a lower error, 0.1 being an obvious example. I'm pretty sure (but I don't know how to prove…) that if you integrate the absolute error over any sufficiently large[1] set of reals, it's higher in Guido's proposal than in the current design.

[1]: The "sufficiently large" there is just so you don't choose a set so small that all of its elements map to a single IEEE double, like { 0.1 <= x < 0.100000000000000001 }.

Similarly, today, the distribution of float values across the real

number line is... not uniform (because of exponents), and I don't know the right technical term, you know what it looks like. The distribution of repr-of-float variables is not.

Nope, sorry, I don't get you.

I think that what you are trying to say is that for each binary exponent, the floats with that same exponent are distributed uniformly:

|...|...|...|...|...|...|...|...|

but their reprs are not:

|...|..|..|....|...|....|..|....|

Is this what you mean?

That's part of it. But on top of that, I think (but again can't prove) that the reprs are on average skewed further to the left than the right, which means that, e.g., the average of a collection of reprs-of-floats will also be skewed in that direction.

So, those are my three points. Remember that I don't know if any of them actually matter to anyone, and in fact am hoping they do not.

9:16 a.m.

From: Mark H. Harris harrismh777@gmail.com Sent: Friday, March 7, 2014 11:50 PM

On Saturday, March 8, 2014 12:52:32 AM UTC-6, Mark H. Harris wrote:

Guido, Andrew, I have a question about repr() in my current codes for 3.3.4 and below:

Would it be reasonable to add a function to pdeclib to be used internally like this:

def Dec(numform): return Decimal(repr(numform))

Yes. If you know that repr(numform) is the string decimal representation that you want for numform, then Decimal(repr(numform)) is guaranteed to be the decimal floating-point value that you want. So if numform is a float, in Python 3.3, given your examples, then this is exactly what you want.

Does this work correctly (for pdeclib internally) ? I think this works better than my first

stab at this (just using str() ) because repr() acts like an eval() ??

I think you're a little confused here.

First, in 3.3, for float, repr and str return the same thing. You may be working with other types for which that isn't true—but in that case, you need to figure out which one gives you the decimal representation you want, and use that one, whether it's repr or str (or something else).

Second, repr() doesn't act like an eval(). It's sort of an _inverse_ of eval, for some types.[1] For example, the repr of Decimal('0.1') is the string "Decimal('0.1')", and eval("Decimal('0.1')") will give you Decimal('0.1'). [1] Please don't rely on this. First, it's not true for all types. Second, even if you restrict yourself to types where it is true, repr is not the best way to exchange or persist values, and eval leads to dangerous and fragile code. But this is very useful for things like playing with Python at the interactive interpreter.

It looks like what you are proposing will fix the problem I've been whining about. Thank you.

I think you may be better off using an explicit repr, even if numform is guaranteed to be a float. It makes your intentions clear: you want the one and only Decimal value that matches the decimal string representation of numform, not just arbitrarily any of the many Decimal values that can round-trip back to the float numform, right?

Of course if at all possible, the best thing to do is to avoid creating floats in the first place. Use Decimal('0.1') for constants in your code, use Decimal(user_input) rather than converting user_input with float or eval and then trying to recover the lost information later, etc. But obviously there are cases where you can't do this, because you've, e.g., received the number as a 64-bit IEEE double over the wire from some other program you don't control. That's where Guido's proposal would help you.

10:43 a.m.

On Sat, Mar 8, 2014 at 12:10 AM, Guido van Rossum guido@python.org wrote:

>

*decimal digits* of
precision from a float with 53 *bits* -- at least not by default. Maybe we
can relent on this guarantee and make the constructor by default use fewer
digits (e.g. using the same algorithm used by repr(<float>)) and have an
optional argument to produce the full precision? Or reserve
Decimal.from_float() for that?

Using the same algorithm as used by repr(float) feels like a bad idea to me: my gut feeling is that a conversion from float to Decimal is a fundamental operation that should be easily describable in simple terms, and should not involve the level of complication and 'magic' involved in the float repr. (That level of magic seems fine for the repr, because in most cases you don't actually care too much what string you get so long as it evaluates back to the correct float; you're not doing arithmetic with the result.)

IEEE 754-2008 requires simply that conversions between numbers in different formats round correctly. It specifies a "convertFormat" operation, described as follows:

""" If the conversion is to a format in a different radix or to a narrower precision in the same radix, the result shall be rounded as specified in Clause 4. Conversion to a format with the same radix but wider precision and range is always exact. ... """

... where clause 4 is the one that outlines the general rounding rules that apply to almost all IEEE 754 operations.

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

Option 2 would seem closest to what IEEE 754 specifies. To augment option 2, we could also make it easier to create Decimal contexts corresponding to the IEEE 754 recommended interchange formats (decimal32, decimal64, decimal128). This was already proposed in http://bugs.python.org/issue8786.

Mark

11:01 a.m.

On Sat, Mar 8, 2014 at 9:43 AM, Mark Dickinson dickinsm@gmail.com wrote:

>

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

Proposals for change should also take into consideration that Decimal
already does *exact* conversions for integers (and I believe has done since
it first existed). It would be quite surprising for `Decimal(2**1000)`

and
`Decimal(2.0**1000)`

to be different numbers. If we change the float
behaviour, we might also want to change conversion from int to round to the
nearest Decimal using the current context. Again, that's closer to what
IEEE 754 specifies for the "convertFromInt" operation.

On the other hand, we probably shouldn't lend *too* much weight to IEEE
754, especially when talking about choice of precision. IEEE 754 isn't a
perfect fit for Decimal: the IEEE standard is mostly concerned with fixed
width decimal formats, which is subtly different from Mike Cowlishaw's
approach of "extensible precision" where the precision is not so closely
tied to the format. Python's decimal module is based on Cowlishaw's
standard, not on IEEE 754.

Mark

12:20 p.m.

On 3/7/2014 11:44 PM, Guido van Rossum wrote:

I agree. But what do you think of my main proposal? I would retract it if you advised me so.

I'd like to hear what Mark Dickinson has to say. I assume like most people he lost interest in this thread. I've sent him a message asking him to take a look.

Eric.

1:01 p.m.

On Sat, 08 Mar 2014 01:14:46 -0500 Terry Reedy tjreedy@udel.edu wrote:

On 3/7/2014 2:20 PM, Guido van Rossum wrote:

However, for the growing contingent of scientists who use Python as a replacement for Matlab (not Mathematica!), it could be a big nuisance. They don't care about decimal issues (they actually like binary better)

One reason knowledgeable users of floating point numbers as approximations like binary better is that the binary floating point system is much 'smoother' than the decimal floating point system. For example, with 3 decimal digits, consider .999, 1.00, 1.01. The first difference is .001, the second is .01, 10 x larger. This is one of the problems of working with base 10 slide rules. For binary, the largest ratio between differences is 2 rather than 10.

Well, can you explain what difference it does in practice?

Regards

Antoine.

3:05 p.m.

On 03/07/2014 01:01 PM, Mark H. Harris wrote:

The point here is that binary floats should not be promoted to decimal floats by using "exact" copy from binary float representation to decimal float representation.

In the case of writing out a number. Decimal(2.1), A decimal literal solves that case since no floats are involved. It would be the same as the exact copy, but that is perfectly expected as well. (and already works when a string is used in the constructor.

Regarding not raising an error when a float is used directly in the decimal constructor: As wrong as it may seem at first, also consider python does not raise an error for this.

```
>>> int(.1)
0
```

The users is expected to know that the result will not always equal to what is given. It's just really obvious in this case because, this is what this function has always done, and what we expect it to do.

The decimal case isn't that different except that it isn't so obvious. What that means is the docs needs be adjusted to make it more easily findable... But currently...

Help on class Decimal in module decimal:

class Decimal(builtins.object) | Decimal(value="0", context=None): Construct a new Decimal object. | value can be an integer, string, tuple, or another Decimal object. | If no value is given, return Decimal('0'). The context does not affect | the conversion and is only passed to determine if the InvalidOperation | trap is active. |

Which says nothing about using floats, and should if it is allowed. It does talk about floats in the from_float method including comments on exact representation.

Help on built-in function from_float:

from_float(...) method of builtins.type instance from_float(f) - Class method that converts a float to a decimal number, exactly. Since 0.1 is not exactly representable in binary floating point, Decimal.from_float(0.1) is not the same as Decimal('0.1').

```
>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal.from_float(float('nan'))
Decimal('NaN')
>>> Decimal.from_float(float('inf'))
Decimal('Infinity')
>>> Decimal.from_float(float('-inf'))
Decimal('-Infinity')
```

So I think the first case should say, it uses from_float() when a floating point is used, and explicitly also say to look at the from_float docs. (or better yet include them directly in the class doc string.

As far as Ulps go. Unless the average error can be reduced I don't see any reason to change it. If the docs say it's uses from_float and from_float gives the same result. I think it's perfectly reasonable and likely to answer most users questions where they expect to find the answer.

Cheers, Ron

3:15 p.m.

On Sat, Mar 8, 2014 at 12:01 PM, Antoine Pitrou solipsis@pitrou.net wrote:

problems of working with base 10 slide rules. For binary, the largest ratio between differences is 2 rather than 10.

Well, can you explain what difference it does in practice?

Probably not much that the average user would care about, but there are a whole host of 'nice' properties that work in binary floating-point but not in decimal. For example, in binary, assuming IEEE 754, round-to-nearest, etc., you're guaranteed that for any two representable floats x and y, the "average" (x+y)/2 lies in the interval [x, y] (even strictly between x and y provided that x and y are at least 2 ulps apart), so a naively written floating-point bisection search will converge. In decimal that's not true: you can lose a whole digit of precision when adding x and y and end up with a result that's outside [x, y].

from decimal import *

getcontext().prec = 3

x = Decimal('0.516')

y = Decimal('0.518')

(x + y) / 2

Decimal('0.515') # ouch!

Then if you're doing numerical analysis and error computations, the "wobble" (the variation in scale of the ratio between the mathematical relative error and the error expressed in ulps) is 2 for binary, 10 for decimal. That makes for weaker error bounds and faster-growing errors for operations done in decimal floating-point rather than binary. (And it's even worse for hexadecimal floating-point, which is why IEEE 754 is a big improvement over IBM's hex float format.)

Binary is just better for serious numerical work. :-)

Mark

3:18 p.m.

Mark Dickinson dickinsm@gmail.com wrote:

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

I agree that these are the main reasonable options. All functions in Cowlishaw's specification use all digits from overlong input operands, so there should be a convenient way of creating such operands exactly.

I think we should not make an exeception for floats, unless we move into the direction of IEEE 754-2008 (but that surely is a different topic).

Some general comments:

The current model works well, since we already have a fallback constructor that rounds:

from decimal import * context = getcontext() Decimal(1.1) Decimal('1.100000000000000088817841970012523233890533447265625') context.create_decimal(1.1) Decimal('1.10')

If you want safety against accidental conversions, set FloatOperation:

context.traps[FloatOperation] = True Decimal(1.1) Traceback (most recent call last): File "<stdin>", line 1, in <module> decimal.FloatOperation: [<class 'decimal.FloatOperation'>]

If you want extra safety against *any* accidental float
comparisons,
check the flags manually. This is necessary, since equality operations
cannot raise due to the fact that they are also used for membership
testing:

context.clear_flags() Decimal(9) in [9.0] True if context.flags[FloatOperation]: ... raise FloatOperation ... Traceback (most recent call last): File "<stdin>", line 2, in <module> decimal.FloatOperation

You see that the float operation is still recorded, even though it isn't raised automatically. Such a check could be done periodically or at the end of a program.

To summarize, I think we should leave things as they are or turn on FloatOperation by default.

Stefan Krah

5:13 p.m.

On Saturday, March 8, 2014 2:16:26 AM UTC-6, Andrew Barnert wrote: >

I think you're a little confused here.

repr() doesn't act like an eval(). It's sort of an _inverse_ of eval, for some types.[1] For example, the repr of Decimal('0.1') is the string "Decimal('0.1')", and eval("Decimal('0.1')") will give you Decimal('0.1').

You are correct, I was confused about the inverse relationship, for some types, because the http doc says:

```
For many types, this function makes an attempt to return a
```

string that would

```
yield an object with the same value when passed to eval(),
```

But, the system doc help() makes things more clear:

repr(...) repr(object) -> string

```
Return the canonical string representation of the object.
For most object types, eval(repr(object)) == object.
```

Thank you, your explanation of the caveats for repr() were helpful to me.

marcus

5:54 p.m.

I'll try to respond to Mark Dickinson's second message (and nothing else that happens in the thread since last night), because (a) it concisely summarizes his position and (b) brings up a new strawman.

On Sat, Mar 8, 2014 at 2:01 AM, Mark Dickinson dickinsm@gmail.com wrote:

On Sat, Mar 8, 2014 at 9:43 AM, Mark Dickinson dickinsm@gmail.com wrote:

>

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

I think you're writing this entirely from the POV of an expert in floating point. And I'm glad we have experts like you around! I don't consider myself an expert at all, but I do think I have something to bring to the table -- insight the experience of non-expert users.

When a non-expert writes Decimal(1.1), each of the three above outcomes surprises. We know that (1) was unpopular, that's why we changed it. We now know that (3) is unpopular at least in some circles (Mark Harrison can't be the only one who doesn't like it). Changing to (2) wouldn't do much to address this, because the default context has way more precision than float, so it still shows a lot of extraneous digits.

Yes, it's trivial to get rid of those extra digits, just use quotes. But if
*my* proposal were adopted, it would be trivial for numerical experts to
get the extra digits -- just use from_float(). At this point, my claim is
that we're talking essentially about what is the better experience for most
users, and while I am not much of a user of Decimal myself, I believe that
my proposal has benefits more people and situations than it has downsides.

Proposals for change should also take into
consideration that Decimal
already does *exact* conversions for integers (and I believe has done since
it first existed). It would be quite surprising for `Decimal(2**1000)`

and
`Decimal(2.0**1000)`

to be different numbers.

This feels like a strawman, since Decimal(2**1000 + 1) and
Decimal(2.0**1000 + 1) produce different outcomes (the latter gives a lot
of digits but is one too small), and the similar examples with a large
exponent (10000) differ dramatically (the float version raises an
exception).

For most purposes it's a fluke that 2.0**1000 can be represented exactly as a float, and the argument doesn't convince me at all. There are just too many odd examples like that, and it always will remain a minefield.

If we change the float behaviour, we might also want to change conversion from int to round to the nearest Decimal using the current context. Again, that's closer to what IEEE 754 specifies for the "convertFromInt" operation.

I don't think that's no the table. Python's int doesn't lose precision, and
users know this and depend on it. (In fact, I find ignoring the current
context in the constructor a totally reasonable approach -- it does this
uniformly, regardless of the argument type. My proposal doesn't change
this, the context would *still* not be used to decide how a float is
converted. Only the fact that it's a float would.)

>

On the other hand, we probably shouldn't lend
*too* much weight to IEEE
754, especially when talking about choice of precision. IEEE 754 isn't a
perfect fit for Decimal: the IEEE standard is mostly concerned with fixed
width decimal formats, which is subtly different from Mike Cowlishaw's
approach of "extensible precision" where the precision is not so closely
tied to the format. Python's decimal module is based on Cowlishaw's
standard, not on IEEE 754.

I wonder what Cowlishaw would say about our current discussion. He is also the father of Rexx...

-- --Guido van Rossum (python.org/~guido)

6:35 p.m.

On 03/08/2014 01:08 AM, Steven D'Aprano wrote:

On Fri, Mar 07, 2014 at 06:02:02PM -0800, Guido van Rossum wrote:

On Fri, Mar 7, 2014 at 5:05 PM, Steven D'Apranosteve@pearwood.info wrote:

The issue (as I see it) is that there are many different decimals d that all convert to the same float f (because of rounding). The d that is constructed by taking the exact value of f is gross overkill.

Decimal -> float is many-to-one: more than one Decimal will round to a single float. But float -> Decimal is always one-to-one, I think, regardless of whether you use the current exact conversion or repr first. The crux of the matter is whether or not it is overkill for Decimal to use the exact value.

Correct me if I'm wrong, but I think we agree that there ought to be a way to convert floats exactly to Decimal, we just disagree on whether that ought to be spelled Decimal(x) or Decimal.from_float(x).

Decimal(x), just like you do... float(x) when x is an int.

I think the from_float on the decimal type was an attempt to solve a problem that should have been solved by more explicit docs on the decimal class. A constructor should call the type to_type method when it's higher precision type.

```
float(decimal('2.1')) # float call's decimals to_float() method.
int(decima('2.1')) # int call's decimals to_int() mehtod. *1
1.[* or __int__]
```

Going in the other direction is different. They aren't symmetric operations and to think they are is a mistake.

```
float(int('2.1')) # float knows how to convert ints.
decimal(float('2.1)) # decimal knows how to convert floats.
```

What this says if an object can supply it's own converter for less accurate types, but should ask the other how to covert for more accurate types. (or ask if it doesn't know how to covert.)

So the decimal.from_float method is redundant, as it was combined into the constructor. (which is the more consistent to python interface. I'm not sure if that was the main reason, but it makes sense to me.)

Likewise I think we agree that there ought to be some way to convert floats to the nearest "simple" Decimal, with the question being whether that ought to be spelled Decimal(repr(x)) or Decimal(x).

It should be this... I'm absolutely certain! :-)

```
Decimal(str(n))
```

No problems with that as long as it's an explicit choice.

And by adding a decimal literal, we avoid the float to decimal conversion completely for setting decimal value constants and entering in raw data by hand.

```
2.1d
```

The use of str(n) above is more about getting a human readable form.

When converting the internal value of a float to a decimal, it should equal the floats exact value. A repr should give the exact value its object if it's suppose to be a machine readable version of it. (As numbers __repr__ should do.)

The __str__ method should be the human readable version. Possibly the console can have a setting to use str() in place of repr(). So when you just use the console as a calculator, it will work more like one.

In other words, don't fix decimal, because floats repr isn't showing it's exact value. That's really an issue with float, not decimal. And don't use repr() if you want a nice human readable value, use str().

If we're all in agreement that this is the only serious area of disagreement for a change which has any hope of appearing in 3.5, then we can put aside the more speculative proposals

The following seem like important relationships to me.

For all numbers (n), with exact representation in int.

```
'1' -> int -> float -> decimal (exact)
decimal' -> float -> int -> '1' True
>>> n = D(float(int(1)))
>>> int(float(n))
1
```

For all numbers (n), with exact representations in float.

```
n -> float -> decimal (exact)
decimal -> float -> n True
>>> n = D(float('2.5'))
>>> float(n)
2.5
```

Is this valid in python 3.4?

When going from higher precision to lower precision values, (as stored internally), the best answer is to use the best value with the lowest possible error. (Not necessarily the one that looks the nicest.)

I say potential, because if you don't know what the error may be, you shouldn't guess. It's possible someone somewhere is studying exact float representations as a set. ;-)

Data input inaccuracies are for the user to determine and handle, but python should supply the tools to help them do that. It just shouldn't do it prematurely.

Cheers, Ron

6:43 p.m.

On Sun, Mar 9, 2014 at 4:35 AM, Ron Adam ron3200@gmail.com wrote:

A repr should give the exact value its object if it's suppose to be a machine readable version of it. (As numbers __repr__ should do.)

As I understand it, float.__repr__ does indeed give the exact value, in terms of reconstructing the float. There are infinitely many float literals that will result in the exact same bit pattern, so any of them is valid for repr(n) to return.

1.233999999999999985 == 1.234 True repr(1.233999999999999985) '1.234'

ChrisA

7:10 p.m.

On 03/08/2014 11:43 AM, Chris Angelico wrote:

On Sun, Mar 9, 2014 at 4:35 AM, Ron Adamron3200@gmail.com wrote:

A repr should give the exact value its object if it's suppose to be a machine readable version of it. (As numbers __repr__ should do.)

As I understand it, float.__repr__ does indeed give the exact value, in terms of reconstructing the float.

What I'm thinking about is...

If floats repr is changed to disregard exta digits, will this still be true? How is float to know what exta digits should be disregarded?

There are infinitely many float literals that will result in the exact same bit pattern, so any of them is valid for repr(n) to return.

When are float literals actually converted with floats. It seems to me, that the decimal functions can be used to get the closest one. Then they will be consistent with each other. (if that isn't already being done.)

Cheers, Ron

7:11 p.m.

On 8 March 2014 16:54, Guido van Rossum guido@python.org wrote:

I'll try to respond to Mark Dickinson's second message (and nothing else that happens in the thread since last night), because (a) it concisely summarizes his position and (b) brings up a new strawman.

On Sat, Mar 8, 2014 at 2:01 AM, Mark Dickinson dickinsm@gmail.com wrote: >

On Sat, Mar 8, 2014 at 9:43 AM, Mark Dickinson dickinsm@gmail.com wrote: > >

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

I think you're writing this entirely from the POV of an expert in floating point. And I'm glad we have experts like you around! I don't consider myself an expert at all, but I do think I have something to bring to the table -- insight the experience of non-expert users.

Standards compliance is important though. Mark is essentially restating something that is repeated hundreds of times throughout various floating point standards documents: a result may be exact or it must be correctly rounded according to the current context (with appropriate flags set and traps fired).

It is not mandated that results requiring more precision than the current context be exact. It is mandated that if a result is to be inexact then the implementation must use a very specific type of inexactness. I don't believe that a standards-compliant decimal module has any wiggle room to invent a new kind of rounding (which I think would be required to achieve what you suggest).

When a non-expert writes Decimal(1.1), each of the three above outcomes surprises. We know that (1) was unpopular, that's why we changed it. We now know that (3) is unpopular at least in some circles (Mark Harrison can't be the only one who doesn't like it). Changing to (2) wouldn't do much to address this, because the default context has way more precision than float, so it still shows a lot of extraneous digits.

Yes, it's trivial to get rid of those extra digits, just use quotes. But if
*my* proposal were adopted, it would be trivial for numerical experts to get
the extra digits -- just use from_float(). At this point, my claim is that
we're talking essentially about what is the better experience for most
users, and while I am not much of a user of Decimal myself, I believe that
my proposal has benefits more people and situations than it has downsides.

If you write Decimal(1.1) and are surprised by the result then you have misunderstood something. It may be that you have little understanding of the difference between binary and decimal floating point (but then why are you using Decimal?). Perhaps you don't fully understand the literal->float->Decimal pathway that occurs when the expression is evaluated because you're new to Python or just haven't really thought about it before.

In any case if the result of Decimal(1.1) surprises you then it's because you're expecting it do something that should be done in a different way. Hiding the extra digits does not help a user to understand how to use Decimal.

I actually use this in teaching to demonstrate how binary floating point works. I think it's important when teaching my students for them to understand that the following is a lie:

```
>>> a = 1.1
>>> a
1.1
```

The helpful digit-hiding repr is lying to you. There is no float with the value 1.1. I can sort-of demonstrate this with some arithmetic:

```
>>> 0.1 + 0.11 - 0.11 - 0.1
1.3877787807814457e-17
```

But that gives the misleading impression that inexact arithmetic is the source of the error. It's important to understand that the error occurs straight away in the literal "1.1". I demonstrate this by showing that

```
>>> from decimal import Decimal
>>> a = 1.1
>>> Decimal(a)
Decimal('1.100000000000000088817841970012523233890533447265625')
```

which also shows the true value stored in the float.

The fact that I use this in teaching is not supposed to serve as an argument for keeping it (I could just as easily use from_float). My point is that showing the digits helps someone to understand what is going on. If a user is converting float to Decimal without knowing what they're doing then the extra digits are a clue that they don't fully understand what's happening and haven't necessarily used the best approach.

There is a good solution to the problem of non-experts wanting to write 1.1 and get the exact value 1.1: decimal literals. With that they can just write 1.1d and not need to learn any more about it. Earlier in this thread you reject that idea saying that you can't teach it to "newbies": ''' Maybe we can add a new literal notation meaning 'decimal'; that would be a relatively straightforward change to the parser (once the new decimal extension module is incorporated), but it would not do much to avoid surprising newbies (certainly you can't go teaching them to always write 3.14d instead of 3.14). However, it would probably help out frequent users of Decimal. (Then again, they might not be using literals that much -- I imagine the source of most such programs is user or file input.) ''' I disagree. If you're at the point of wanting to use the Decimal module then you're at the point where it's reasonable to learn about Decimal literals.

Also I've written code using the decimal module for high precision calculation and it has lots of decimal literals. That is I do (and I see other's doing)

```
>>> from decimal import Decimal as D
>>> d = D('1e-20')
```

even though this is never used in the decimal documentation. This is the closest you can get to decimal literals right now.

Oscar

7:43 p.m.

From: Ron Adam ron3200@gmail.com

Sent: Saturday, March 8, 2014 10:10 AM

On 03/08/2014 11:43 AM, Chris Angelico wrote:

On Sun, Mar 9, 2014 at 4:35 AM, Ron Adamron3200@gmail.com wrote:

A repr should give the exact value its object if it's suppose to be a machine readable version of it. (As numbers __repr__ should do.)

As I understand it, float.__repr__ does indeed give the exact value, in terms of reconstructing the float.

What I'm thinking about is...

If floats repr is changed to disregard exta digits, will this still be true? How is float to know what exta digits should be disregarded?

No one is suggesting such a change, and it would be shot down if anyone did.

The old repr and str for float used to discard (different numbers of) digits. The current version does not. Instead, if picks the shortest string that would evaluate back to the same value if passed to the float constructor (or to eval).

So, repr(0.100000000000000006) == '0.1', not because repr is discarding digits, but because 0.100000000000000006 == 0.1 (because the closest binary IEEE double value to both is 0.1000000000000000055511151231257827021181583404541015625).

There are infinitely many float literals that will result in the exact

same bit pattern, so any of them is valid for repr(n) to return.

When are float literals actually converted with floats. It seems to me, that the decimal functions can be used to get the closest one. Then they will be consistent with each other. (if that isn't already being done.)

Float literals are converted to floats at compile time. When the compiler sees 0.1, or 0.100000000000000006, it works out the nearest IEEE double to that literal and stores that double.

So, by the time any Decimal function sees the float, there's no way to tell whether it was constructed from the literal 0.1, the literal 0.100000000000000006, or some long chain of transcendental functions whose result happened to have a result within 1 ulp of 0.1.

The current behavior guarantees that, for any float, float(Decimal(f)) == float(repr(Decimal(f))) == f. Guido's proposal would preserve that guarantee. If that's all you care about, nothing would change. Guido is just suggesting that, instead of using the middle Decimal from the infinite set of Decimals that would make that true, we use the shortest one.

7:49 p.m.

On Sat, Mar 8, 2014 at 4:54 PM, Guido van Rossum guido@python.org wrote:

When a non-expert writes Decimal(1.1), each of the three above outcomes surprises. We know that (1) was unpopular, that's why we changed it. We now know that (3) is unpopular at least in some circles (Mark Harrison can't be the only one who doesn't like it). Changing to (2) wouldn't do much to address this, because the default context has way more precision than float, so it still shows a lot of extraneous digits.

That at least could be addressed by making the default context one that
corresponds to IEEE's decimal64, which has a precision of 16 decimal
digits; there doesn't seem to be any particularly good rationale for the
current choice of default context. And that would address your point
(which I agree with) that `Decimal(x)`

currently shows way more digits than
are useful. It wouldn't address Mark Harris's needs, though, and would in
some sense make things worse for non-expert users: for *most* floats x,
Decimal(x) would then give what the user expects purely because 16 digits
is a good match for the binary float precision, but for a small fraction of
inputs it would give a surprising result.

from decimal import Decimal, getcontext

getcontext().prec = 16

+Decimal(1.1) # + to simulate rounding to 16 digits

Decimal('1.100000000000000')

+Decimal(1.2)

Decimal('1.200000000000000')

+Decimal(9.7)

Decimal('9.699999999999999')

This feels like a strawman, since Decimal(2**1000 + 1) and

Decimal(2.0**1000 + 1) produce different outcomes (the latter gives a lot of digits but is one too small), and the similar examples with a large exponent (10000) differ dramatically (the float version raises an exception).

For most purposes it's a fluke that 2.0**1000 can be represented exactly as a float, and the argument doesn't convince me at all. There are just too many odd examples like that, and it always will remain a minefield.

Accepted, but I believe the proposal would break a number of other
expectations too with respect to ints and floats. For one, we currently
have the unsurprising, and I believe desirable, property that conversion to
Decimal is monotonic: for any finite numbers (int, float or Decimal) x and
y, if x <= y then Decimal(x) <= Decimal(y). The proposal would break that
property, too: you could find examples of an integer x and float y such
that `x < y`

and `Decimal(x) > Decimal(y)`

would be
simultaneously True.

x = 49534541648432951

y = x + 2.0

x < y

True

Decimal(x) > Decimal(repr(y)) # simulating proposed meaning of Decimal(y)

True

I'm still struggling a bit to express exactly what it is that bothers me so much about the proposal. It's a combination of the above with:

there's an obvious

*correct*way to convert any value to Decimal, and that's to round to the context precision using the context rounding mode - that's what's done almost universally for Decimal arithmetic operations; it feels wrong that something as direct as`Decimal(x)`

would do anything else (the current exact conversions actually*do*rankle with me a bit, but this is what I'd replace them with)this proposal would not play well with other binary number formats if they were ever introduced: if we introduced a float128 type (or a float32 type), it would be awkward to reconcile the proposed conversion with a conversion from float128 to Decimal. That's mostly because the semantics of the proposed conversion use information about the

*format*of the input type, which is unusual for floating-point and floating-point standards: operations tend to be based purely on the*values*of the inputs, disregarding the formats.if we're aiming to eliminate surprises, the 'fix' doesn't go far enough: Decimal(1.1 + 2.2) will still surprise, as will Decimal(0.12345123451234512345)

contrarily, the fix goes too far: it feels wrong to be changing the semantics of float to Decimal conversion when the real problem is that the user wants or expects floating-point literals to represent decimal numbers.

If the proposal goes forward, I'll live with it, and will simply avoid
using the `Decimal`

type to convert from floats or ints. But I'd really
prefer to keep the short Decimal(x) spelling as something simple and
non-magic, and find more complicated ways (quotes!) of spelling the magic
stuff.

I wonder what Cowlishaw would say about our current discussion. He is also

the father of Rexx...

I wonder that, too.

I've said too much already; beyond registering my strong -1 on this proposal, I'm going to keep out of further discussion.

Mark

7:51 p.m.

On Saturday, March 8, 2014 4:01:47 AM UTC-6, Mark Dickinson wrote

On the other hand, we probably shouldn't lend *too* much weight to IEEE

754, especially when talking about choice of precision. IEEE 754 isn't a perfect fit for Decimal: the IEEE standard is mostly concerned with fixed width decimal formats, which is subtly different from Mike Cowlishaw's approach of "extensible precision" where the precision is not so closely tied to the format. Python's decimal module is based on Cowlishaw's standard, not on IEEE 754.

hi Mark, Mike's notes include the following:

-- the package meets the requirements of IEEE 854-1987 (with minor restrictions discussed below), including support for subnormal numbers, -0, NaNs, infinities, etc. It also conforms to the floating-point arithmetic definition in ANSI X3.274-1996.

```
here: http://grouper.ieee.org/groups/754/email/msg00429.html
```

The extensible precision is what makes the standard interesting.

marcus

7:55 p.m.

On Sat, 8 Mar 2014 18:49:02 +0000 Mark Dickinson dickinsm@gmail.com wrote:

If the proposal goes forward, I'll live with it, and
will simply avoid
using the `Decimal`

type to convert from floats or ints. But I'd really
prefer to keep the short Decimal(x) spelling as something simple and
non-magic, and find more complicated ways (quotes!) of spelling the magic
stuff.

For the record, as a non-float expert, Mark's arguments convince me.

Just my 2 cents ;-)

Regards

Antoine.

7:59 p.m.

Thanks Oscar, that's a very well-reasoned post.

On Sat, Mar 8, 2014 at 10:11 AM, Oscar Benjamin

oscar.j.benjamin@gmail.comwrote:

On 8 March 2014 16:54, Guido van Rossum guido@python.org wrote:

I'll try to respond to Mark Dickinson's second message (and nothing else that happens in the thread since last night), because (a) it concisely summarizes his position and (b) brings up a new strawman.

On Sat, Mar 8, 2014 at 2:01 AM, Mark Dickinson dickinsm@gmail.com wrote:

On Sat, Mar 8, 2014 at 9:43 AM, Mark Dickinson dickinsm@gmail.com wrote:

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

I think you're writing this entirely from the POV of an expert in floating point. And I'm glad we have experts like you around! I don't consider myself

insight the experience of non-expert users.

Standards compliance is important though. Mark is essentially restating something that is repeated hundreds of times throughout various floating point standards documents: a result may be exact or it must be correctly rounded according to the current context (with appropriate flags set and traps fired).

I have mixed feelings about such standards. I can see its importance. But like the Unicode standard, it seems to want to grab authority over areas that I think belong to the language design. Also at this point claiming "compliance" with some standard is usually a morass of weasel-words rather than clearly implementing a spec.

It is not mandated that results requiring more precision than the current context be exact. It is mandated that if a result is to be inexact then the implementation must use a very specific type of inexactness. I don't believe that a standards-compliant decimal module has any wiggle room to invent a new kind of rounding (which I think would be required to achieve what you suggest).

That would be unfortunate.

Yes, it's trivial to get rid of those extra digits, just use quotes. But
if
*my* proposal were adopted, it would be trivial for numerical experts to
get
the extra digits -- just use from_float(). At this point, my claim is
that
we're talking essentially about what is the better experience for most
users, and while I am not much of a user of Decimal myself, I believe
that
my proposal has benefits more people and situations than it has
downsides.

If you write Decimal(1.1) and are surprised by the result then you have misunderstood something. It may be that you have little understanding of the difference between binary and decimal floating point (but then why are you using Decimal?). Perhaps you don't fully understand the literal->float->Decimal pathway that occurs when the expression is evaluated because you're new to Python or just haven't really thought about it before.

Ah, but I'm not surprised. I'm unsatisfied. I understand what led to the result, but it's still not what I want, and it's a pain to train myself to do the extra thing that gives me what I want.

In any case if the result of Decimal(1.1) surprises you then it's because you're expecting it do something that should be done in a different way. Hiding the extra digits does not help a user to understand how to use Decimal.

But does showing the extra digits do anything to help? It's just as likely to teach them a trick (add quotes or a str() call) without any new understanding.

I actually use this in teaching to demonstrate how binary floating point works. I think it's important when teaching my students for them to understand that the following is a lie:

```
>>> a = 1.1
>>> a
1.1
```

The helpful digit-hiding repr is lying to you. There is no float with the value 1.1. I can sort-of demonstrate this with some arithmetic:

```
>>> 0.1 + 0.11 - 0.11 - 0.1
1.3877787807814457e-17
```

But that gives the misleading impression that inexact arithmetic is the source of the error. It's important to understand that the error occurs straight away in the literal "1.1". I demonstrate this by showing that

```
>>> from decimal import Decimal
>>> a = 1.1
>>> Decimal(a)
Decimal('1.100000000000000088817841970012523233890533447265625')
```

which also shows the true value stored in the float.

The fact that I use this in teaching is not supposed to serve as an argument for keeping it (I could just as easily use from_float). My point is that showing the digits helps someone to understand what is going on. If a user is converting float to Decimal without knowing what they're doing then the extra digits are a clue that they don't fully understand what's happening and haven't necessarily used the best approach.

I don't think every user of Decimal necessarily needs to be an expert capable of explaining what's going on. Certainly that's not needed to be an effective user of float -- the anomalies are explained to most people's satisfaction by some hand-waving about imprecise results.

There is a good solution to the problem of non-experts wanting to write 1.1 and get the exact value 1.1: decimal literals. With that they can just write 1.1d and not need to learn any more about it. Earlier in this thread you reject that idea saying that you can't teach it to "newbies": ''' Maybe we can add a new literal notation meaning 'decimal'; that would be a relatively straightforward change to the parser (once the new decimal extension module is incorporated), but it would not do much to avoid surprising newbies (certainly you can't go teaching them to always write 3.14d instead of 3.14). However, it would probably help out frequent users of Decimal. (Then again, they might not be using literals that much -- I imagine the source of most such programs is user or file input.) ''' I disagree. If you're at the point of wanting to use the Decimal module then you're at the point where it's reasonable to learn about Decimal literals.

You're right that I dismissed it too quickly. 3.14d is clearly even better than Decimal(3.14) doing the right thing. It is also still a lot more work (touches many parts of the code rather than just the Decimal class).

Also I didn't realize that the C-implemented decimal module was already used in CPython (so I thought it would be even more work).

Also I've written code using the decimal module for high precision calculation and it has lots of decimal literals. That is I do (and I see other's doing)

```
>>> from decimal import Decimal as D
>>> d = D('1e-20')
```

even though this is never used in the decimal documentation. This is the closest you can get to decimal literals right now.

Right.

But I still have this nagging feeling that the precision Decimal(<float>)
currently gives you is, in a sense, *fake*, given that the input has much
less precision.

-- --Guido van Rossum (python.org/~guido)

8:13 p.m.

On Saturday, March 8, 2014 12:49:02 PM UTC-6, Mark Dickinson wrote:

- if we're aiming to eliminate surprises, the 'fix' doesn't go far enough:
Decimal(1.1 + 2.2) will still surprise, as will {snip}

Correct. That is why a decimal literal notation is also needed. I've emulated it here:

from pdeclib import *
sqrt(1.1+2.2)**2
Decimal('3.30000000000000026645352591003756970167159') <==== this is a
surprise, to a naive user
sqrt(d(1.1)+d(2.2))**2
Decimal('3.29999999999999999999999999999999999999999') <=== this if
decimal literal,

```
sqrt(1.1d + 2.2d)
```

```
But, you are correct that what is "really" wanted
--someday-- is to
```

have the literal be decimal (rather than float) to begin with.

Neither here nor there, step at a time over time is better than simple status quo.

Please let me be clear, I think Guido's proposal is a very good first step. It makes sense for the most users (esp naive ones) and does not interfere with advanced users.

marcus

8:47 p.m.

On 03/08/2014 12:43 PM, Andrew Barnert wrote:

From: Ron Adam ron3200@gmail.com

Sent: Saturday, March 8, 2014 10:10 AM

On 03/08/2014 11:43 AM, Chris Angelico wrote:

On Sun, Mar 9, 2014 at 4:35 AM, Ron Adamron3200@gmail.com wrote:

As I understand it, float.__repr__ does indeed give the exact value, in terms of reconstructing the float.

What I'm thinking about is...

If floats repr is changed to disregard exta digits, will this still be true? How is float to know what exta digits should be disregarded?

No one is suggesting such a change, and it would be shot down if anyone did.

Glad to hear it! :-)

The old repr and str for float used to discard (different numbers of) digits. The current version does not. Instead, if picks the shortest string that would evaluate back to the same value if passed to the float constructor (or to eval).

So, repr(0.100000000000000006) == '0.1', not because repr is discarding digits, but because 0.100000000000000006 == 0.1 (because the closest binary IEEE double value to both is 0.1000000000000000055511151231257827021181583404541015625).

There are infinitely many float literals that will result in the exact

same bit pattern, so any of them is valid for repr(n) to return.

When are float literals actually converted with floats. It seems to me, that the decimal functions can be used to get the closest one. Then they will be consistent with each other. (if that isn't already being done.)

Float literals are converted to floats at compile time. When the compiler sees 0.1, or 0.100000000000000006, it works out the nearest IEEE double to that literal and stores that double.

So, by the time any Decimal function sees the float, there's no way to tell whether it was constructed from the literal 0.1, the literal 0.100000000000000006, or some long chain of transcendental functions whose result happened to have a result within 1 ulp of 0.1.

The current behavior guarantees that, for any float, float(Decimal(f)) == float(repr(Decimal(f))) == f. Guido's proposal would preserve that guarantee. If that's all you care about, nothing would change. Guido is just suggesting that, instead of using the middle Decimal from the infinite set of Decimals that would make that true, we use the shortest one.

Sounds good to me..

Can that effect rounding where a value may round down instead of up? or vice versa. If so, it should be the shortest string that does not cross the mid point of the two closest floats. (I think I got that right.)

Not sure where I read it last night, but there was a mention that only a few languages do the this conversion with less than .5 Ulps error. But it seems to me it might be more important to not to error in the wrong direction if there is a choice.

Well I'll leave it up you guys, But it's a very interesting topic for sure.

Cheers, Ron

9:12 p.m.

On Sun, Mar 9, 2014 at 6:13 AM, Mark H. Harris harrismh777@gmail.com wrote:

```
But, you are correct that what is "really"
wanted --someday-- is to
```

have the literal be decimal (rather than float) to begin with.

Neither here nor there, step at a time over time is better than simple status quo.

Please let me be clear, I think Guido's proposal is a very good first step. It makes sense for the most users (esp naive ones) and does not interfere with advanced users.

It's probably time someone [1] wrote up a PEP about all this. The most important points, as I see it, are:

1) Create a new Decimal literal notation - 1.234d seems to be the most popular syntax. This is reasonably uncontroversial, but it has consequences. 2) Create a new float literal notation - 1.234f or 1.234r or any of the other proposals. 3) Possibly change repr(float) to include the tag. 4) Introduce a "from __future__ import decimal_literals" (named to parallel unicode_literals - you can get a u"literal" without that directive, but the default literal type becomes unicode) 5) What about int/int? Should that now be Decimal? Should it be per-module??? 6) Further cans of worms like #5

Introducing 1/2/4 would let you stick the future directive into PYTHONSTARTUP and then run Python as an interactive decimal calculator.

ChrisA

[1] Not it!

9:16 p.m.

On Saturday, March 8, 2014 12:49:02 PM UTC-6, Mark Dickinson wrote:

- if we're aiming to eliminate surprises, the 'fix' doesn't go far enough: Decimal(1.1 + 2.2) will still surprise, as will {snip}

On the other hand, how is this now possible?

====== RESTART ========================== from pdeclib import * d(1.1+2.2) Decimal('3.3') sqrt(1.1+2.2) Decimal('1.81659021245849499925351968583091621951684') sqrt(1.1+2.2)**2 Decimal('3.29999999999999999999999999999999999999999')

def sqrt(x): """ sqrt(x) square root function

```
(x may be string, int, float, or decimal)
(returns decimal rounded to context precision)
"""
y=x.__round__(15)
with localcontext(ctx=None) as cmngr:
cmngr.prec+=14
sqr=Decimal(repr(y)).sqrt()
return +sqr
```

What say you?

9:25 p.m.

On Saturday, March 8, 2014 2:23:23 PM UTC-6, Mark H. Harris wrote: > >

```
y=x.__round__(15) <====== how to
set the round within context so
```

that works for float literals & decimals with localcontext(ctx=None) as cmngr: {snip}

Is this possible..?

Only do this for float literals. Yes? Set a trap, and choose.

I gotta play with this a bit...

9:32 p.m.

On 8 March 2014 18:59, Guido van Rossum guido@python.org wrote:

On Sat, Mar 8, 2014 at 10:11 AM, Oscar Benjamin oscar.j.benjamin@gmail.com wrote: >

On 8 March 2014 16:54, Guido van Rossum guido@python.org wrote:

If you write Decimal(1.1) and are surprised by the result then you have misunderstood something.

Ah, but I'm not surprised. I'm unsatisfied. I understand what led to the result, but it's still not what I want, and it's a pain to train myself to do the extra thing that gives me what I want.

That "you" wasn't directed at *you* personally (and neither are the ones
below).

If you're using the Decimal module then it is precisely because you do need to care about rounding/accuracy etc. If you want to use it but aren't prepared to take the effort to be careful about how to pass exact input to the constructor then you're basically taking an impossible position that no one else can help you with.

In any case if the result of Decimal(1.1) surprises you then it's because you're expecting it do something that should be done in a different way. Hiding the extra digits does not help a user to understand how to use Decimal.

But does showing the extra digits do anything to help? It's just as likely to teach them a trick (add quotes or a str() call) without any new understanding.

It's enough to make you think "Why did that happen?". It's clear when you see those digits that you have not created an object with the exact value that you wanted. The obvious next question is "How do I make it do what I want?". The docs can lead you very quickly to the correct way of doing it: http://docs.python.org/3.4/library/decimal.html#quick-start-tutorial

There is a good solution to the problem of non-experts wanting to write 1.1 and get the exact value 1.1: decimal literals. With that they can just write 1.1d and not need to learn any more about it.

You're right that I dismissed it too quickly. 3.14d is clearly even better than Decimal(3.14) doing the right thing. It is also still a lot more work (touches many parts of the code rather than just the Decimal class).

Also I didn't realize that the C-implemented decimal module was already used in CPython (so I thought it would be even more work).

As Stefan mentioned earlier there are other issues to resolve around how a decimal literal should work. It's not obvious that there should be a straight-forward equivalence between 3.14d and D('3.14'). Perhaps there should be new thread to consider how to potentially do that (and whether or not it's worth it).

But I still have this nagging feeling that the
precision Decimal(<float>)
currently gives you is, in a sense, *fake*, given that the input has much
less precision.

That depends what you use it for. My most common reason for converting a float to a Decimal is to test the accuracy of a float-based calculation by comparing it against the corresponding much higher-precision decimal calculation e.g.:

```
with localcontext() as ctx:
ctx.prec = 100
error = f(D(x)) - D(f(x))
```

For this I want the constructor to give me the exact value of the float x.

Oscar

10:01 p.m.

On Saturday, March 8, 2014 2:16:00 PM UTC-6, Mark H. Harris wrote: > > >

On Saturday, March 8, 2014 12:49:02 PM UTC-6, Mark Dickinson wrote:

- if we're aiming to eliminate surprises, the 'fix' doesn't go far enough: Decimal(1.1 + 2.2) will still surprise, as will {snip}

How about this then: I think I've got it...

=====
RESTART =========================
from pdeclib import *
d(1.1+2.2)
Decimal('3.3')
sqrt(1.1+2.2)**2
Decimal('3.29999999999999999999999999999999999999999')
x=d(1.1)
y=d(2.2)
sqrt(x+y)**2
Decimal('3.29999999999999999999999999999999999999999')

Code Below--------------------------

#
*****/

# sqrt(x) return decimal sqrt(x) rounded to context precision

#
*****/

def sqrt(x): """ sqrt(x) square root function

```
(x may be string, int, float, or decimal)
(returns decimal rounded to context precision)
"""
with localcontext(ctx=None) as cmngr:
cmngr.prec+=14
if (isinstance(x, float)==True):
y=x.__round__(15)
sqr=Decimal(repr(y)).sqrt()
else:
sqr=Decimal(x).sqrt()
return +sqr
```

-------------------- what say you? --------------------- marcus

10:32 p.m.

On 8 March 2014 18:55, Antoine Pitrou solipsis@pitrou.net wrote:

On Sat, 8 Mar 2014 18:49:02 +0000 Mark Dickinson dickinsm@gmail.com wrote: >

If the proposal goes forward, I'll live with it,
and will simply avoid
using the `Decimal`

type to convert from floats or ints. But I'd really
prefer to keep the short Decimal(x) spelling as something simple and
non-magic, and find more complicated ways (quotes!) of spelling the magic
stuff.

For the record, as a non-float expert, Mark's arguments convince me.

Just my 2 cents ;-)

I am in the same position. I "instinctively" wanted Decimal(1.1) to return the same value as Decimal('1.1'), but as I read Mark's argument, it became clearer to me that doing so involves a deliberate loss of precision that is not present anywhere else in the chain of conversions.

literal 1.1 -> float 1.1 is a precise conversion, in the sense that no other float better represents the (decimal) literal 1.1. The current float 1.1 -> decimal conversion is exact.

Converting float 1.1 to Decimal('1.1') deliberately drops precision, even though an exact conversion is possible. I don't think that this is something that should happen implicitly.

On the other hand, the *reason* people do Decimal(1.1) is because they
want a decimal value of 1.1. (Doh.) The fact that this isn't what they
are actually calculating is a mistake they make because Python doesn't
make it easy to *get* the decimal value 1.1 (I know the correct
approach is Decimal('1.1'), but I don't think that is obvious or
intuitive[1]). Decimal literals (1.1d) would resolve this issue
without introducing implicit rounding operations.

Paul

[1] The reason Decimal('1.1') feels non-obvious to me is that it feels
like it's getting to a decimal "via" a string. Of course, Decimal(1.1)
is also a going "via" a float. The *value* of the current behaviour is
that it leads people to an understanding that this is what it is.

10:33 p.m.

On Sat, Mar 8, 2014 at 10:49 AM, Mark Dickinson dickinsm@gmail.com wrote:

Accepted, but I believe the proposal would break a
number of other
expectations too with respect to ints and floats. For one, we currently
have the unsurprising, and I believe desirable, property that conversion to
Decimal is monotonic: for any finite numbers (int, float or Decimal) x and
y, if x <= y then Decimal(x) <= Decimal(y). The proposal would break that
property, too: you could find examples of an integer x and float y such
that `x < y`

and `Decimal(x) > Decimal(y)`

would be
simultaneously True.

I think that Mark Dickinson's point about breaking monotonicity is a STRONG argument against the proposal to change the meaning of Decimal(float_num) to produce Decimal(repr(float_num)). That latter spelling is already available if anyone wants it.

The focus on float value that have been entered as literals feels like a
distraction to me. Floats come from other places as well, and making
Decimal(float_num) produce the *exact* value feels far more desirable than
producing "something which rounds to the same float.

As I think Guido has acknowledged in a recent post, a far better and more
intuitive approach is just to make decimal literals easier to write.
Teaching users to write '3.14d' doesn't feel that hard to me, and it looks
notably prettier than 'decimal.Decimal("3.14")'. A *would* also like an
optional explicit binary-float literal though, e.g. '3.14f'. This wouldn't
actually mean anything different from '3.14', but then "PI" doesn't mean
anything different from u"PI" either. The optional bin-float specifier, in
principle, allows for some Python 4000 in which decimal is the default
literal (although I don't think I'd ever support that, but no harm in
letting that decision be made years from now).

- if we're aiming to eliminate surprises, the 'fix' doesn't go far enough:
Decimal(1.1 + 2.2) will still surprise, as will Decimal(0.12345123451234512345)

This is a simple illustration of why the "do what I mean" goal of the
proposed change will fall apart rather quickly for anything but the
simplest examples. The status quo of Decimal(float_num) producing the
*exact* value continues to feel the best to me.

I've said too much already; beyond registering my strong -1 on this

proposal, I'm going to keep out of further discussion.

So yeah, me too: -1 on proposal.

10:35 p.m.

On Mar 8, 2014, at 13:01, "Mark H. Harris" harrismh777@gmail.com wrote:

On Saturday, March 8, 2014 2:16:00 PM UTC-6, Mark H. Harris wrote:

On Saturday, March 8, 2014 12:49:02 PM UTC-6, Mark Dickinson wrote:

- if we're aiming to eliminate surprises, the 'fix' doesn't go far enough: Decimal(1.1 + 2.2) will still surprise, as will {snip} How about this then: I think I've got it...

===== RESTART =========================
from pdeclib import *
d(1.1+2.2)
Decimal('3.3')
sqrt(1.1+2.2)**2
Decimal('3.29999999999999999999999999999999999999999')
x=d(1.1)
y=d(2.2)
sqrt(x+y)**2
Decimal('3.29999999999999999999999999999999999999999')

Code Below--------------------------

#
*****/

# sqrt(x) return decimal sqrt(x) rounded to context precision

#
*****/

def sqrt(x): """ sqrt(x) square root function

```
(x may be string, int, float, or decimal)
(returns decimal rounded to context precision)
"""
with localcontext(ctx=None) as cmngr:
cmngr.prec+=14
if (isinstance(x, float)==True):
y=x.__round__(15)
sqr=Decimal(repr(y)).sqrt()
else:
sqr=Decimal(x).sqrt()
return +sqr
```

-------------------- what say you? ---------------------

It looks like you're trying to emulate a pocket calculator here. The question is, why are you accepting floats in the first place if that's your goal?

10:43 p.m.

Guido van Rossum guido@python.org wrote:

I wonder what Cowlishaw would say about our current discussion. He is also the father of Rexx...

It's in the second paragraph here:

http://speleotrove.com/decimal/daconvs.html

My reading is that he prefers exact conversions (if possible). This does not surprise me: Cowlishaw is a proponent of using all information from overlong inputs, which all functions of the specification do (he recently mentioned in a private mail that earlier versions of the specification did not have this feature and it was initially added for Java's BigDecimal -- he called it "an improvement").

Stefan Krah

10:54 p.m.

On Saturday, March 8, 2014 3:35:14 PM UTC-6, Andrew Barnert wrote:

It looks like you're trying to emulate a pocket calculator here. The

question is, why are you accepting floats in the first place if that's your goal?

That's fair... if it were me, or you, I wouldn't. In fact, if it were just me, or just you, I don't think any of us would be talking about this--- I wouldn't even have brought it up.

I am advocating for people--- I am going to say, " if you want just a tad more than double just to check things out, please use the decimal module and import pdeclib. These folks are what Guido called newbies, I'll call them naive users, and these folks are using a calculator (like the TI89) and they will be prone to keying in sqrt(2.345) /

Its just easier than explaining to them (at this point) all the stuff they need to understand to use the functions by making sure they feed decimals to the function; either by quoting their numeric inputs, or even using a decimal literal, &c. They know what the functions do, and will want to use them to get more digits, but may not remember the rules to get the "correct" digits. So I'm advocating for them, to make it easier.

If it were just me, I'd just make sure the functions got decimals and be done with it.

If you look at my square root function, its really not right; but its closer. For instance if you give it 1/3 it still is only slightly better than before. I don't have all the junk after the 16 th digit, but, I only have .33333333 to 15 places. So, believe me, I know there is no one truly good answer here. Just trying to make it easier for the most people.

marcus

10:59 p.m.

On 03/08/2014 12:32 PM, Oscar Benjamin wrote:

On 8 March 2014 18:59, Guido van Rossum wrote: >

But I still have this nagging feeling that the
precision Decimal(<float>)
currently gives you is, in a sense, *fake*, given that the input has much
less precision.

That depends what you use it for. My most common reason for converting a float to a Decimal is to test the accuracy of a float-based calculation by comparing it against the corresponding much higher-precision decimal calculation e.g.:

```
with localcontext() as ctx:
ctx.prec = 100
error = f(D(x)) - D(f(x))
```

For this I want the constructor to give me the exact value of the float x.

I am not a mathematician, and it's been a long time since I took physics, but I seem to remember that a lot of importance was placed on significant digits.

So, how is this justified?

Python 3.4.0b3+ (default:aab7258a31d3, Feb 7 2014, 10:48:46) [GCC 4.7.3] on linux Type "help", "copyright", "credits" or "license" for more information. --> from decimal import Decimal as D --> 9017.0109812864350067128347806 9017.010981286436 --> D(9017.0109812864350067128347806) Decimal('9017.01098128643570817075669765472412109375')

In case my question isn't obvious, the direct float got me 16 digits, while the Decimal float got me 42 digits. Why is the Decimal more "accurate" that the float it came from?

-- ~Ethan~

11:15 p.m.

On Mar 8, 2014, at 12:12, Chris Angelico rosuav@gmail.com wrote:

It's probably time someone [1] wrote up a PEP about all this.

The most important points, as I see it, are:

1) Create a new Decimal literal notation - 1.234d seems to be the most popular syntax. This is reasonably uncontroversial, but it has consequences.

1a) Move the Decimal type's implementation out of the module and into Objects, or make it a frozen module that's always loaded at startup, or invent some scheme to load it as needed.

1b) Possibly move the type to builtins? (Certainly not necessary--function is a built-in type, and you can create them without importing anything, but the type lives in types, not builtins.)

1c) Possibly add the implementation to the public C API? (With which methods? FromString, but what about From/AsDouble? Is there any need to From/AsTuple from C? Inspect the object in any other way? Get/set the current context?)

2) Create a new float literal notation - 1.234f or 1.234r or any of the other proposals. 3) Possibly change repr(float) to include the tag.

3a) Change repr(Decimal) to use the new literal.

4) Introduce a "from __future__ import decimal_literals" (named to parallel unicode_literals - you can get a u"literal" without that directive, but the default literal type becomes unicode) 5) What about int/int? Should that now be Decimal? Should it be per-module??? 6) Further cans of worms like #5

Introducing 1/2/4 would let you stick the future directive into PYTHONSTARTUP and then run Python as an interactive decimal calculator.

11:25 p.m.

On Sun, Mar 9, 2014 at 9:15 AM, Andrew Barnert abarnert@yahoo.com wrote:

3) Possibly change repr(float) to include the tag.

3a) Change repr(Decimal) to use the new literal.

I'd put that in the consequences of part 1 - it's pretty obvious that, with a decimal literal, the repr of a Decimal should be that. It'd affect people's tests but shouldn't break much else. But changing repr(float) means tagging everything, rather than just having a new convenient notation for Decimal. It's the same physical/technical change but it has a lot more implication :)

And yeah. You listed several significant quinseconses, and I've no doubt there'll be others.

ChrisA

11:27 p.m.

On Mar 8, 2014, at 13:54, "Mark H. Harris" harrismh777@gmail.com wrote:

On Saturday, March 8, 2014 3:35:14 PM UTC-6, Andrew Barnert wrote:

It looks like you're trying to emulate a pocket calculator here. The question is, why are you accepting floats in the first place if that's your goal?

That's fair... if it were me, or you, I wouldn't. In fact, if it were just me, or just you, I don't think any of us would be talking about this--- I wouldn't even have brought it up.

I am advocating for people--- I am going to say, " if you want just a tad more than double just to check things out, please use the decimal module and import pdeclib. These folks are what Guido called newbies, I'll call them naive users, and these folks are using a calculator (like the TI89) and they will be prone to keying in sqrt(2.345) /

So your goal is that if someone is a TI89 expert but a Python novice, they can easily port a function from their TI89 to Python (or maybe even write a TI89-like desktop calculator program) with your library? That doesn't seem too unreasonable of a goal.

The problem is that you're not quite achieving it. If you want a 16-digit-display calculator, using a mix of float64 values and decimals that expand as needed for intermediates is not equivalent to using 20-digit fixed decimals. So they will not get the same results for many functions that they port. And if they really care about having more than a few digits of precision, they either care about these differences, or really really should care. Hiding them in many but not all cases doesn't seem to be doing them a service.

A simpler solution might be to just only support a fixed precision of, say, 8 digits. Then you can handle float inputs without running into that one-too-few 3's problem you mentioned. But obviously this isn't great either--you'd just end up with users who say "your library is perfect for me, except that I need 10 digits, not 8"...

11:36 p.m.

On 8 March 2014 20:12, Chris Angelico rosuav@gmail.com wrote: >

It's probably time someone [1] wrote up a PEP about all this. The most important points, as I see it, are:

1) Create a new Decimal literal notation - 1.234d seems to be the most popular syntax. This is reasonably uncontroversial, but it has consequences.

I feel like I've boxed myself into the corner by arguing that
*someone* should do this. :)

Unless someone else especially wants the mantle I'll try to write a PEP about decimal literals (I won't have any real time until Monday though).

Oscar

9 Mar
9 Mar

12:16 a.m.

On 3/7/2014 5:12 AM, Steven D'Aprano wrote:

For example, between 1 and 100000, about 12% of integer-valued floats fail the "1/(1/n) == n" test, but over 51% of the integer-valued Decimals:

py> from decimal import Decimal as D py> sum(1/(1/n) != n for n in range(1, 100001)) 11846 py> sum(1/(1/D(n)) != n for n in range(1, 100001)) 51665

Likewise we can test how many fail the "(1/n)*n == 1" test:

py> sum((1/n)*n != 1 for n in range(1, 100001))
13117
py> sum((1/D(n))*n != 1 for n in range(1, 100001))
36806

13% for floats versus 36% for Decimals.

One more to prove it isn't a fluke: the "sqrt(n)**2 == n" test:

py> sum((n**0.5)**2 != n for n in range(1, 100001))
49544
py> sum((n**D("0.5"))**2 != n for n in range(1, 100001))
71303

That's three for three in favour of binary floats.

Thank you for these concrete examples. Yesterday I posted that binary floats are better because they are smoother, so that the relative approximation is more constant. I would not be surprised if this is behind the numbers above. In any case, the above convince me more strongly that we should stay with binary floats as default.

-- Terry Jan Reedy

12:20 a.m.

[Mark Dickinson]

Python's decimal module is based on Cowlishaw's standard, not on IEEE 754.

[Guido]

I wonder what Cowlishaw would say about our current discussion. He is also the father of Rexx...

I _expect_ Mike likes the status quo. Explaining why by analogy:

I'm certain the 754 designers would not like the status quo. To them string<->number conversions were "operations", no different in principle than the operations of, say, addition or root extraction. And _all_ operations in 754 treat the input(s) as infinitely precise, and produce an output correctly rounded according to the current context. Now that's technically not so for float->string operations in 754, but that's an irrelevant distraction (754 allowed for weaker rounding guarantees in that specific context because nobody knows how to achieve correct rounding efficiently in all cases in that context - and the members of the 754 committee later said they regretted allowing this exception).

Bottom line here: for all the 754 designers, and regardless of the type of x, Decimal(x) should accept x as exactly correct and produce a decimal object correctly rounded according to the current context (including setting context flags appropriately - e.g., signaling the inexact exception if any information in the input was lost due to rounding).

Now the specific instance of this Mike did pronounce on is Decimal(string). It's obvious that the 754 view is that the assumed-to-be infinitely precise string literal be rounded to the current context's precision (etc). But Mike (in email at the time) explicitly wanted to retain all the string digits in the returned decimal object, wholly ignoring the current context.

So that's what Python does. Is Decimal(0.1) really different? The 754 view, once you're used to it, is utterly predictable: Whatever the internal representation of 0.1, it's assumed to be infinitely precise, and you round its value to the decimal context's current precision. Mike's view is usually the same, but in one specific case of construction he thought differently. My guess is that he'd choose to be consistent with "the rule" for construction, having made an exception for it once already, than choose to be consistent with the other decimal operations.

Or we could hark back to REXX's desire to present an arithmetic that works the way people learned in school. I'd try that, except I'm not sure kids today learn arithmetic in school any more beyond learning how to push buttons ;-)

12:33 a.m.

On Saturday, March 8, 2014 4:27:25 PM UTC-6, Andrew Barnert wrote:

So your goal is that if someone is a TI89 expert but a Python novice, they can easily port a function from their TI89 to Python (or maybe even write a TI89-like desktop calculator program) with your library? That doesn't seem too unreasonable of a goal.

Your points are all mostly valid, taking all assumptions into account. Some of these folks actually have TI89's but don't really know how to use them yet. Some of them have TI84+, or TI83, some TI Nspire... some others. Mostly the comparison between the calc and python isn't the point. The point is using python to do the trig (or whatever) and along the way introducing programming. Its just one use case that is not too different than a thousand other "average" or naive user cases, where the folks are still on the learning curve but need to be introduced to computers and computer science as well as the maths. This will also be good for folks who know what they are doing and will now be able to do it just a little more efficiently. For folks who really know what they are doing spelling issues are not even a problem. So, we can help newbie/naive users without impacting advanced users like yourself.

The bottom line is trying to eliminate as few surprises as possible, making the learning curve as easy and fun as possible. The point is not to make a calculator; rather to make a decimal floating point package for python that is flexible. Experienced users can use it for all kinds of purposes, and newbies can use it too. The experts on this list, like you, will have no problem using the package (if you want) and folks that are brand new to python will be able to use it too.

At some point the decimal concepts will come up, and the decimal literal will come in very handy then. For folks using 3.3.4 or below obviously the caveats are going to need to be explained in the documentation... and my functions are going to need to take in floats and do something meaningful with them, as you saw in the square root routine. Its a little more hassle for me to code and maintain, but its less "surprise" impact on users will be worth it (esp for young wanna bee naiveté types).

marcus

12:36 a.m.

On Sat, Mar 8, 2014 at 11:54 AM, Guido van Rossum guido@python.org wrote: >

On Sat, Mar 8, 2014 at 2:01 AM, Mark Dickinson dickinsm@gmail.com wrote: >

On Sat, Mar 8, 2014 at 9:43 AM, Mark Dickinson dickinsm@gmail.com wrote: > >

I see three sane options for float to Decimal conversion:

- Raise an exception.
- Round to the nearest Decimal value using the current context for that round operation.
- Do what we're currently doing, and do an exact conversion.

I think you're writing this entirely from the POV of an expert in floating point. And I'm glad we have experts like you around! I don't consider myself an expert at all, but I do think I have something to bring to the table -- insight the experience of non-expert users.

This reminded me a discussion we had with Mark Dickinson on the bug tracker: "When a user is entering 0.6112295, she means 0.6112295, not 0x1.38f312b1b36bdp-1 or 0.61122949999999998116351207499974407255649566650390625 which are exact values of the underlying binary representation of 0.611229