No, it's not a terminology difference. That's why it won't work. You
can't have some code execute to change how the parser works. Parse
time happens before execution time.
If I understand correctly, the IEEE 754-2008 decimal formats are fixed size,
so even the largest of them (128 bit)
http://en.wikipedia.org/wiki/Decimal128_floating-point_format
is fixed size limited to 34 digits.
But the current Ruby BigDecimal implementation can go well beyond that:
$ ruby -v
ruby 1.9.3p0 (2011-10-30 revision 33570) [i686-linux]
002:0> require 'bigdecimal'
=> true
003:0> require 'bigdecimal/util'
=> true
005:0> a =
BigDecimal.new("123456789012345678901234567890123456789012345678901234567890")
=> #<BigDecimal:985aa30,'0.1234567890 1234567890 1234567890 1234567890
1234567890 123456789E60',63(72)>
006:0> b = a +1
=> #<BigDecimal:9859324,'0.1234567890 1234567890 1234567890 1234567890
1234567890 1234567891E60',63(144)>
007:0> b - a
=> #<BigDecimal:98639a0,'0.1E1',9(81)>
008:0> (b - a).to_s
=> "0.1E1"
009:0> (b - a).to_s('F')
=> "1.0"
That means to remain truely backwards compatible, we would need something
like:
1.1 #=> Float (machine dependent)
1.1BD #=> BigDecimal ("unlimited" size)
1.1D #=> Decimal (e.g. picking the standardized IEEE 754 2008 Decimal128
fomat)
HTH,
Peter
···
On Fri, Jan 27, 2012 at 8:51 AM, Matthias Wächter <matthias@waechter.wiz.at>wrote:
Am 25.01.2012 15:28, schrieb botp:
2012/1/25 Matthias Wächter<matthias@waechter.wiz.**at<matthias@waechter.wiz.at>
>:I’m curious to see what’s gonna happen once Desktop CPUs start to support
the nice decimal floating-point types of IEEE 754-2008
[http://en.wikipedia.org/wiki/**IEEE_754-2008#Basic_formats<http://en.wikipedia.org/wiki/IEEE_754-2008#Basic_formats>\]
… maybe Ruby
should already prepare syntactically for support of these types. And, in
theory, there is already a library from Intel
[http://software.intel.com/en-**us/articles/intel-decimal-**
floating-point-math-library/<Intel® Decimal Floating-Point Math Library;
]
one can use to do decimal floating-point math.wow, thanks for the update. i hope ruby can indeed support that even
if i need to recompile manually.
Floating points are a great choice for approximating continuous values and
thus working on things which require both high performance and
approximating real-world data sources. This includes things like games,
non-gaming related 3D applications, perceptual media including audio and
video codecs, and everything involved in working with perceptual media on
computers such as non-linear editing, speech synthesis, and speech
recognition.
People don't often do these things in Ruby. I'd say they're uncommon use
cases.
Something people do quite often in Ruby: deal with money. Money isn't
continuous, it's discrete. A decimal type is a much better choice for
dealing with money than a floating point.
As I said before, I think Ruby could benefit from decimal literals. They
can't solve all use cases. They can't be a replacement for rationals, such
as in the 1D/3D example (actually I'm not entirely convinced of that
either, but that's a sidebar)
I have found many uses for BigDecimal before and have seen Fixnums used
where BigDecimal would probably be more appropriate (i.e. "count cents, not
dollars!") where having (Big)Decimal literals would probably change
people's minds about that sort of thing.
···
On Mon, Jan 30, 2012 at 12:22 AM, Robert Klemme <shortcutter@googlemail.com>wrote:
It seems many people use the "floating point mess" without major
issues. So it cannot be as bad as you make it sound.
--
Tony Arcieri
>>
>>
>> >> What 'value' do you expect for this expression:
>> >>
>> >> BigDecimal("1.0") / BigDecimal("3.0")
>> >
>> > Decimal math operations need use a default rounding context or require one to set a
>> > rounding context before performing an operation.
>> >
>> > Do you round towards +infinity, -infinity,
>> > towards 0, away from 0,
>> > to nearest (and if equidistant, round down),
>> > to nearest (and if equidistant, round up), or
>> > to nearest (and if equidistant, round so that the last digit is even) aka. bankers rounding?
>> >
>> > Only then can you know what to expect.
>>
>> Agree 100%. I was just trying to illustrate that even if you have some
>> wonderfully crafted big decimal literal syntax, you still need to address
>> the context issues you just listed.
>>
>> 1.0D / 3.0D
>>
>> Might be easier to type but it is only part (perhaps a small part) of the puzzle.
>
> The point of offering something other than "all you get is floats and
> libraries", of course, is that a simple set of such rules is a lot easier
> to deal with than the floating point mess that is common now.It seems many people use the "floating point mess" without major
issues. So it cannot be as bad as you make it sound.
"Many" is a relative term -- and "without major issues" is even more
relative. When your definition of "without major issues" is "reinventing
the more-useful-comparison-method all over the world all the time" or
"lots of people unaware of edge-case unexpected values", I'm inclined to
disagree with your definition.
>
> It's a lot
> easier to deal with "it truncates decimals past the Nth place" or any
> other such simple rule than "Uh . . . it's probably going to do some
> weird shit when you start doing math -- weird shit that requires you to
> do binary math to predict the outcome. You really just shouldn't use
> it. Use some verbose library syntax instead. You like typing -- right?"Any other _efficient_ floating point math would have to choose a
limited representation. You would get unexpected (for those who are
not aware of numerics anyway) rounding issues anyway. That would not
be any better than the situation today, which has the advantage of a
long established standard, so it is known to many. Having an
arbitrary precision math as default would make many applications
slower than necessary which do not need that high precision math.
I don't know whether you have been reading the ongoing discussion
subthread involving me, or whether you have the old emails handy -- or
if, conversely, you have been ignoring this particular branch for a while
and deleting the messages unread until now. An examination of the
discussion so far would reveal that I'm not even arguing that the Float
class should necessarily be replaced by any fixed point types as the
default type for decimal literals. All I'm trying to do is point out the
flaws in keeping on with things exactly as they are. Considering the
long tradition of using IEEE-standard floating point numbers behind a
literal representation like 1.1, I have actually been suggesting
something like adding at least one additional comparison method to the
Float class that can be used to perform comparisons up to a given fixed
precision, or do a ratio comparison, or something well-understood that
does not require the programmer to do binary math on the fly to be able
to accurately keep track of the results of Float#== comparisons all the
time or reinvent such comparison methods regularly.
From a usability perspective, leaving it up to the programmer, assuming
all programmers are aware of the difficulties involved in floating point
accuracy and expecting them to implement work-arounds is a really stinky
language design smell, in my opinion.
If you need to do precise math all the time there are other tools
better suited for that. You'll even find free / open source ones:
Computer algebra system - Wikipedia
Are you aware of how silly it is to tell people that wanting to do math
that doesn't surprise them in Ruby is just a non-starter, and they should
go use something else?
So at the moment I believe the current situation is the best
compromise. If you have suggestions for improvements which do not
make things worse for many when avoiding the unexpected behavior I am
curious to hear them. For a new language the situation is totally
different of course.
I think the "best compromise" would be, as I said more than once in this
subthread of discussion, to add a couple more comparison methods to the
Float class, providing easier mental modelling of how math works within a
program, where they're more likely to be noticed and will not require
everyone to create his or her own. I really, *really* don't understand
the resistance to this simple idea. Even if people sometimes have to
create yet another method for specialized purposes, one more comparison
method designed to abstract away the fuzzy edges of the binary arithmetic
behind the scenes for 98% of common cases would be a *huge* improvement,
where right now the situation is that Float#== can easily bite people in
somewhere in the neighborhood of 98% of common cases (reversing the
likelihood of a sane solution).
Just to be perfectly clear, I'm not suggesting we replace Float#==; I'm
suggesting with supplement it. Semantically speaking, the way Float#==
works now (as I understand it) makes perfect sense (note that I have not
actually read the source for Ruby's Float#==, so I'm not sure it doesn't
make less sense than I think). What doesn't make sense to me is that
being the only option in the class.
Can someone please explain to me in clear, direct terms why there is
opposition to the simple expedient of adding at least one additional
comparison method to Float to address the vast majority of casual use
cases without having to resort to reimplementing such comparison methods
over and over again or bringing in additional libraries with onerous
syntactic verbosity?
···
On Mon, Jan 30, 2012 at 05:22:47PM +0900, Robert Klemme wrote:
On Mon, Jan 30, 2012 at 6:56 AM, Chad Perrin <code@apotheon.net> wrote:
> On Mon, Jan 30, 2012 at 10:03:04AM +0900, Gary Wright wrote:
>> On Jan 29, 2012, at 2:26 AM, Jon Lambert wrote:
>> > On Jan 27, 2012, at 3:26 PM, Gary Wright wrote:
--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
For an initial test without format checking or accessing
the database the exact type that is saved is indeed not
relevant, so a symbol would be a good idea to reduce
GC effort (thanks).
But, in this test, I really needed to actually save to
the database (and that "balance" column is obviously
in "decimal" format). So at that time it would need to be
in the exact format.
The reason I needed a "real" save is that I have delegated
this "balance" to a different model and I had forgotten the
:autosave option on the association, which made the
save of the associated model fail in the condition that the
current model was not changed? The actual test code is
more complex and involves saving, and finding the record
back from the database and then checking for the value.
But I see your point, for simpler test cases a symbol would
be a nice and more efficient way to test this.
Peter
···
On Mon, Jan 23, 2012 at 6:38 PM, Chad Perrin <code@apotheon.net> wrote:
On Mon, Jan 23, 2012 at 08:38:56PM +0900, Peter Vandenabeele wrote:
>
> Thanks for the idea.
>
> I fixed my feature request with this code in
> a library file:
>
> require 'bigdecimal'
> class ::String
> def to_bd
> BigDecimal.new(self)
> end
> end
>
> So it only works on strings (that's what I want).
>
> Now I can say:
>
> describe "balance setter and getter" do
> subject.balance = "15.4".to_bd
> subject.balance.should == "15.4".to_bd
> endIs there any reason this would not be better done with symbols than with
strings?
Ruby is pretty dynamic. It seems entirely reasonable to consider whether
Float could be redefined to dispatch to BigDecimal except when called
explicitly, for instance.
···
On Tue, Jan 24, 2012 at 05:45:18AM +0900, Steve Klabnik wrote:
No, it's not a terminology difference. That's why it won't work. You
can't have some code execute to change how the parser works. Parse
time happens before execution time.
--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
That means to remain truely backwards compatible, we would need something
like:1.1 #=> Float (machine dependent)
1.1BD #=> BigDecimal ("unlimited" size)
1.1D #=> Decimal (e.g. picking the standardized IEEE 754 2008 Decimal128
fomat)
I don't think it'd be very Ruby like to have a separate literal syntax
depending on the size of the number. Some precedent:
1000000000000000.class
=> Fixnum
10000000000000000000000000000000000000000000000000.class
=> Bignum
···
On Sat, Jan 28, 2012 at 10:43 AM, Peter Vandenabeele <peter@vandenabeele.com > wrote:
--
Tony Arcieri
It seems many people use the "floating point mess" without major
issues. So it cannot be as bad as you make it sound.Floating points are a great choice for approximating continuous values and
thus working on things which require both high performance and
approximating real-world data sources. This includes things like games,
non-gaming related 3D applications, perceptual media including audio and
video codecs, and everything involved in working with perceptual media on
computers such as non-linear editing, speech synthesis, and speech
recognition.
Let me add "statistics gathering" to the list...
People don't often do these things in Ruby. I'd say they're uncommon use
cases.
... and suddenly, you have a _very_ common use case. I have no client
where it doesn't happen.
Graphing is also not uncommon.
Something people do quite often in Ruby: deal with money. Money isn't
continuous, it's discrete. A decimal type is a much better choice for
dealing with money than a floating point.
Yes, but a dedicated money type that encodes the currency is also a much better
choice. Also the standard in handling monetary values is not using a decimal
representation anyways: you just encode the smallest value as an Integer.
As I said before, I think Ruby could benefit from decimal literals. They
can't solve all use cases. They can't be a replacement for rationals, such
as in the 1D/3D example (actually I'm not entirely convinced of that
either, but that's a sidebar)
I have found many uses for BigDecimal before and have seen Fixnums used
where BigDecimal would probably be more appropriate (i.e. "count cents, not
dollars!") where having (Big)Decimal literals would probably change
people's minds about that sort of thing.
Counting cents is perfectly valid, fits every database (even your fancy NOSQL
database that doesn't have a decimal type) and is the proper way to do math
involving money. The base value is not the Dollar, it is the
cent. This doesn't hold true if you venture into the realms of fractional
cents, but thats really uncommon (an can be solved by using Rational).
If money is the reason against float, its the wrong reason. It may be a tempting
error, but it only shows that the developer in question did not read a single
line about handling money in software - which is another common problem, but
not one that you can actually fix by the choice of literals.
···
On Jan 30, 2012, at 9:32 AM, Tony Arcieri wrote:
On Mon, Jan 30, 2012 at 12:22 AM, Robert Klemme > <shortcutter@googlemail.com>wrote:
That's right, lots of use cases. And I bet that in these use cases
the programmer never even wants to test two floats for equality.
Representation problems or not, the probability that two points on a
continuum coincide is vanishingly small. So seeking a way to impose
such a test on the floating point type is inappropriate, as many have
said.
Gavin
···
On Mon, Jan 30, 2012 at 7:32 PM, Tony Arcieri <tony.arcieri@gmail.com> wrote:
Floating points are a great choice for approximating continuous values and
thus working on things which require both high performance and
approximating real-world data sources. This includes things like games,
non-gaming related 3D applications, perceptual media including audio and
video codecs, and everything involved in working with perceptual media on
computers such as non-linear editing, speech synthesis, and speech
recognition.
With all due respect to your suggestion, it is good, but we do need
careful analysis of the precise mathematical properties of such an
supplement. Consider, for example, how would you define the behavior
of the following situation:
// assuming your new Float#approx_equal returns true
// if two float are equal until the first digit after the decimal point
// e.g. 1.11.approx_equal(1.10) # => true
a = 1.11
b = 1.1
a.approx_equal(b) #=> true
(a * 100).approx_equal(b * 100) #=> false
I guess, this again breaks the simple math.
···
On Tue, Jan 31, 2012 at 1:27 AM, Chad Perrin <code@apotheon.net> wrote:
On Mon, Jan 30, 2012 at 05:22:47PM +0900, Robert Klemme wrote:
On Mon, Jan 30, 2012 at 6:56 AM, Chad Perrin <code@apotheon.net> wrote:
> On Mon, Jan 30, 2012 at 10:03:04AM +0900, Gary Wright wrote:Can someone please explain to me in clear, direct terms why there is
opposition to the simple expedient of adding at least one additional
comparison method to Float to address the vast majority of casual use
cases without having to resort to reimplementing such comparison methods
over and over again or bringing in additional libraries with onerous
syntactic verbosity?--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
>>
>>
From a usability perspective, leaving it up to the programmer, assuming
all programmers are aware of the difficulties involved in floating point
accuracy and expecting them to implement work-arounds is a really stinky
language design smell, in my opinion.
Most popular programming languages have that smell. It is also not
uncommon for complex tools to require reading a manual and making
oneself familiar with the features of the tool in order to be able to
use it properly. Heck, there are even tools which require mandatory
training (usually to avoid hurting yourself or someone else).
If you need to do precise math all the time there are other tools
better suited for that. You'll even find free / open source ones:
Computer algebra system - WikipediaAre you aware of how silly it is to tell people that wanting to do math
that doesn't surprise them in Ruby is just a non-starter, and they should
go use something else?
That's not what I suggested. I was talking about "precise math" -
what you find in computer algebra systems.
So at the moment I believe the current situation is the best
compromise. If you have suggestions for improvements which do not
make things worse for many when avoiding the unexpected behavior I am
curious to hear them. For a new language the situation is totally
different of course.I think the "best compromise" would be, as I said more than once in this
subthread of discussion, to add a couple more comparison methods to the
Float class, providing easier mental modelling of how math works within a
program, where they're more likely to be noticed and will not require
everyone to create his or her own. I really, *really* don't understand
the resistance to this simple idea. Even if people sometimes have to
create yet another method for specialized purposes, one more comparison
method designed to abstract away the fuzzy edges of the binary arithmetic
behind the scenes for 98% of common cases would be a *huge* improvement,
where right now the situation is that Float#== can easily bite people in
somewhere in the neighborhood of 98% of common cases (reversing the
likelihood of a sane solution).Just to be perfectly clear, I'm not suggesting we replace Float#==; I'm
suggesting with supplement it. Semantically speaking, the way Float#==
works now (as I understand it) makes perfect sense (note that I have not
actually read the source for Ruby's Float#==, so I'm not sure it doesn't
make less sense than I think). What doesn't make sense to me is that
being the only option in the class.
Thanks for the clarification! Somehow it seemed == should be replaced.
So you want something like this in std lib
class Numeric
def delta_eql? x, delta
(self - x).abs <= delta
end
def factor_eql? x, delta
((self / x) - 1).abs <= delta
end
def log10_eql? x
Math.log10(self).floor == Math.log10(x).floor
end
...
end
The user would still have to decide which to choose and what parameter
to pick for the delta. For that he needs to understand the matter of
numeric math. Good documentation could help though. Maybe these
methods should rather go into Math or Math::Eql - there could be
several more. Question is though whether it would be more confusing
than not to have these methods. A user would still be required to
understand the issues and pick a combination of method and values
appropriate for his use case. It would certainly not prevent these
types of discussions.
Can someone please explain to me in clear, direct terms why there is
opposition to the simple expedient of adding at least one additional
comparison method to Float to address the vast majority of casual use
cases without having to resort to reimplementing such comparison methods
over and over again or bringing in additional libraries with onerous
syntactic verbosity?
There is no method which does not require an additional data item
(what I called "level of imprecision"), what makes it harder to make a
proper choice. See also:
Cheers
robert
···
On Mon, Jan 30, 2012 at 6:27 PM, Chad Perrin <code@apotheon.net> wrote:
On Mon, Jan 30, 2012 at 05:22:47PM +0900, Robert Klemme wrote:
On Mon, Jan 30, 2012 at 6:56 AM, Chad Perrin <code@apotheon.net> wrote:
> On Mon, Jan 30, 2012 at 10:03:04AM +0900, Gary Wright wrote:
>> On Jan 29, 2012, at 2:26 AM, Jon Lambert wrote:
>> > On Jan 27, 2012, at 3:26 PM, Gary Wright wrote:
--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
Except for that whole "parse time is different from run time" part you seem to be blithely ignoring. If you've already parsed float literals, then they're floats and are already lossy.
···
On Jan 23, 2012, at 15:13 , Chad Perrin wrote:
On Tue, Jan 24, 2012 at 05:45:18AM +0900, Steve Klabnik wrote:
No, it's not a terminology difference. That's why it won't work. You
can't have some code execute to change how the parser works. Parse
time happens before execution time.Ruby is pretty dynamic. It seems entirely reasonable to consider whether
Float could be redefined to dispatch to BigDecimal except when called
explicitly, for instance.
"Standard is better than better." -Anon.
Also, you don't need a "to_f" to reproduce this -- just enter "0.5 -
0.4 - 0.1" into any Ruby, Python or JavaScript interpreter -- or a C
or Java program, for that matter -- to get a perfectly consistent, and
consistently surprising, very very small negative number that is not
*quite* zero.
So how to test around this in unit tests? In RSpec, use be_within (née
be_close) [1]; in Wrong (which works inside many test frameworks), use
close_to? [2]
- A
[1]
[2] https://github.com/sconover/wrong/blob/master/lib/wrong/close_to.rb
···
--
Alex Chaffee - alex@stinky.com
http://alexchaffee.com
http://twitter.com/alexch
Yeah, but Fixnum and Bignum have different behaviour. I am pretty sure they
are substitutable for eachother in every way, they just handle different
domains for performance reasons (transparent to the user, without any
reasonable exceptions). Float and BigDecimal, however, do have different
behaviour and hence do have the need to specify which you want.
···
On Sat, Jan 28, 2012 at 11:24 PM, Tony Arcieri <tony.arcieri@gmail.com>wrote:
On Sat, Jan 28, 2012 at 10:43 AM, Peter Vandenabeele < > peter@vandenabeele.com > > wrote:
> That means to remain truely backwards compatible, we would need something
> like:
>
> 1.1 #=> Float (machine dependent)
> 1.1BD #=> BigDecimal ("unlimited" size)
> 1.1D #=> Decimal (e.g. picking the standardized IEEE 754 2008 Decimal128
> fomat)
>I don't think it'd be very Ruby like to have a separate literal syntax
depending on the size of the number. Some precedent:>> 1000000000000000.class
=> Fixnum
>> 10000000000000000000000000000000000000000000000000.class
=> Bignum--
Tony Arcieri
Having worked on these sorts of systems, I really hate them. Having to
constantly multiply and divide by 100 because, sorry, in the real world
it's dollars, not cents, that people actually work with and familiar with,
you leave yourself open for all sorts of off by two orders of magnitude
errors doing these sorts of conversions all over the place.
···
On Mon, Jan 30, 2012 at 1:01 AM, Florian Gilcher <flo@andersground.net>wrote:
On Jan 30, 2012, at 9:32 AM, Tony Arcieri wrote:
> I have found many uses for BigDecimal before and have seen Fixnums used
> where BigDecimal would probably be more appropriate (i.e. "count cents,
not
> dollars!") where having (Big)Decimal literals would probably change
> people's minds about that sort of thing.Counting cents is perfectly valid, fits every database (even your fancy
NOSQL
database that doesn't have a decimal type) and is the proper way to do math
involving money. The base value is not the Dollar, it is the
cent.
--
Tony Arcieri
>
>> It seems many people use the "floating point mess" without major
>> issues. So it cannot be as bad as you make it sound.
>>
>
> Floating points are a great choice for approximating continuous values and
> thus working on things which require both high performance and
> approximating real-world data sources. This includes things like games,
> non-gaming related 3D applications, perceptual media including audio and
> video codecs, and everything involved in working with perceptual media on
> computers such as non-linear editing, speech synthesis, and speech
> recognition.Let me add "statistics gathering" to the list...
> People don't often do these things in Ruby. I'd say they're uncommon use
> cases.... and suddenly, you have a _very_ common use case. I have no client
where it doesn't happen.Graphing is also not uncommon.
>
> Something people do quite often in Ruby: deal with money. Money isn't
> continuous, it's discrete. A decimal type is a much better choice for
> dealing with money than a floating point.Yes, but a dedicated money type that encodes the currency is also a much better
choice. Also the standard in handling monetary values is not using a decimal
representation anyways: you just encode the smallest value as an Integer.
It's easy to focus on a single tree and ignore the forest.
Dealing with money is not the only use case -- and it becomes a lot more
complicated when you have to deal with exchange rates, completely blowing
the "just use cents" solution out of the running as a simple expedient.
Money is an example of the kind of use cases where having something a bit
more accuracy-predictable than Floats without imposing a lot of syntactic
overhead is a good idea. It is not the *only* example. Answering "For
example, there's dealing with money . . ." with a solution that only
works for money is missing the point of an example. In fact, I'd say
that the biggest "use case" is probably the vast range of circumstances
wherein there is not a singular, systematic requirement for unsurprising
arithmetic; it is, instead, the everyday case of doing division in casual
cases where the entire point of a program is not the math itself, but the
math is expected to fit some definition of predictable accuracy.
Consider:
1. averaging (mean) small numbers from large samples for a rating system
2. simulating dice rolling and subsequent math for roleplaying games
3. using irb as a "desktop" calculator
4. figuring out distances for trip planning
5. percentages in myriad circumstances, such as offering comparisons of
relatively small differences between different customer groups' behaviors
as just one small part of a larger reporting program (or, for roughly the
same usage pattern, a ten year old learning to programming doing some
simplistic statistical work involving his or her friends' trends in
favorite ice cream flavors)
Note that none of these is as rigorously and pervasively dependent on
exactitude of painstakingly defined standards for accuracy of sub-1.0
positive numbers as you're likely to find in the work of computer
scientists working on their dissertations or specialized professional
fields, but they can still result in bugs based solely on the use of
floating point numbers with Ruby's Float comparison capabilities, thus
mandating either the use of much more verbose libraries with much more
finnicky syntax or the (re)invention of alternate comparison methods for
the Float class.
Then, of course, there's what may be the biggest use-case of all: people
who are not aware of the problem of the inconsistencies of decimal math
using the IEEE-standard floating point implementation because when they
look at documentation for Ruby's Float class all they see is Float#==
with no alternative comparison methods.
If money is the reason against float, its the wrong reason. It may be a tempting
error, but it only shows that the developer in question did not read a single
line about handling money in software - which is another common problem, but
not one that you can actually fix by the choice of literals.
It's not the only reason, and it's not a reason "against float", either.
It's just a single reason for offering *something* other than the very
limited options we currently have -- which seem designed on the
assumption that any case where the unadorned IEEE-standard floating point
type is an exceedingly rare edge case -- and offering it in a manner that
makes it as close to an equal "citizen" as we reasonably can. As things
currently stand, it seems difficult to claim (with a straight face) that
math involving positive decimal numbers between 0 and 1 without
inaccuracies that are nontrivial to predict for the average coder even
rises to the level of second-class "citizen".
Realistically, I think the IEEE- standard floating point type is itself a
fairly rare edge case compared to other options like "truncation at Nth
decimal place" and "just get me close without a difference of 0.1 between
two potential inputs to a given expression resulting in the precedence of
two items being unexpectedly swapped thanks solely to unexpected rounding
mismatches between binary and decimal numbers."
···
On Mon, Jan 30, 2012 at 06:01:02PM +0900, Florian Gilcher wrote:
On Jan 30, 2012, at 9:32 AM, Tony Arcieri wrote:
> On Mon, Jan 30, 2012 at 12:22 AM, Robert Klemme > > <shortcutter@googlemail.com>wrote:
--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
What exactly are you arguing here -- that there's no such thing as a
solution that is easier to evaluate in one's head so that the results are
not surprising a lot of the time?
···
On Tue, Jan 31, 2012 at 11:35:10AM +0900, Yong Li wrote:
On Tue, Jan 31, 2012 at 1:27 AM, Chad Perrin <code@apotheon.net> wrote:
> On Mon, Jan 30, 2012 at 05:22:47PM +0900, Robert Klemme wrote:
>> On Mon, Jan 30, 2012 at 6:56 AM, Chad Perrin <code@apotheon.net> wrote:
>> > On Mon, Jan 30, 2012 at 10:03:04AM +0900, Gary Wright wrote:
>
> Can someone please explain to me in clear, direct terms why there is
> opposition to the simple expedient of adding at least one additional
> comparison method to Float to address the vast majority of casual use
> cases without having to resort to reimplementing such comparison methods
> over and over again or bringing in additional libraries with onerous
> syntactic verbosity?With all due respect to your suggestion, it is good, but we do need
careful analysis of the precise mathematical properties of such an
supplement. Consider, for example, how would you define the behavior
of the following situation:// assuming your new Float#approx_equal returns true
// if two float are equal until the first digit after the decimal point
// e.g. 1.11.approx_equal(1.10) # => true
a = 1.11
b = 1.1
a.approx_equal(b) #=> true
(a * 100).approx_equal(b * 100) #=> falseI guess, this again breaks the simple math.
--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
>
> From a usability perspective, leaving it up to the programmer, assuming
> all programmers are aware of the difficulties involved in floating point
> accuracy and expecting them to implement work-arounds is a really stinky
> language design smell, in my opinion.Most popular programming languages have that smell. It is also not
uncommon for complex tools to require reading a manual and making
oneself familiar with the features of the tool in order to be able to
use it properly. Heck, there are even tools which require mandatory
training (usually to avoid hurting yourself or someone else).
The fact remains that using it properly consists of having to write
work-arounds. There's a difference between "I can get this to do what I
want simply by knowing what output it produces," on one hand, and "To get
this to do what I want I have to implement a work-around," on the other.
The first is a complex tool, which may or may not be well-designed. The
second is a tool that actually falls short of useful simply because
people refuse to consider that it might be a good idea to design the tool
in such a way designed to maximize its utility. Once again, I'm not
saying Float#== is wrong; I'm saying that Float#== is insufficient, by
itself (see below).
(I've edited a typo in the following quote for clarity going forward.)
>
> Just to be perfectly clear, I'm not suggesting we replace Float#==;
> I'm suggesting we supplement it. Semantically speaking, the way
> Float#== works now (as I understand it) makes perfect sense (note
> that I have not actually read the source for Ruby's Float#==, so I'm
> not sure it doesn't make less sense than I think). What doesn't make
> sense to me is that being the only option in the class.Thanks for the clarification! Somehow it seemed == should be replaced.
So you want something like this in std lib
class Numeric
def delta_eql? x, delta
(self - x).abs <= delta
enddef factor_eql? x, delta
((self / x) - 1).abs <= delta
enddef log10_eql? x
Math.log10(self).floor == Math.log10(x).floor
end
...
endThe user would still have to decide which to choose and what parameter
to pick for the delta. For that he needs to understand the matter of
numeric math. Good documentation could help though. Maybe these
methods should rather go into Math or Math::Eql - there could be
several more. Question is though whether it would be more confusing
than not to have these methods. A user would still be required to
understand the issues and pick a combination of method and values
appropriate for his use case. It would certainly not prevent these
types of discussions.
The following is, of course, only my evaluation of the situation and how
best to handle it; I'm sure wiser heads than mine in the realm of
language design could find deficiencies in my suggestions. I'd like to
know what's deficient, though, so if someone has something to say, please
share.
1. No "require" should be . . . required . . . to make use of an
alternative to Float#==, to maximize availability to people who might
lack the understanding to really grasp the importance of avoiding the
pitfalls of Float#==, thus minimizing the proliferation of programs out
there in the world that have subtle errors in them.
2. Keeping the number of comparison methods to a minimum would help
reduce confusion. I'd say that no more than two additional comparison
methods should be available by default, resulting in a maximum of three
options as "standard" comparison methods. With too many options
available on roughly equal availability footing, some of the programmers
who would ignorantly fail to use a comparison method that depends on a
"require" expression (from point 1) might just default to using Float#==
even when it is suboptimal to do so to avoid having to learn about the
different comparison methods and choose one. Beyond a total of two or
three comparison methods (including Float#==), any additionals should
probably be stuck in something in stdlib to be called with "require" as
needed.
3. At least one additional comparison method should have syntax roughly
as succinct as Float#==, though without sacrificing clarity, thus
requiring some careful thought about how to name such a beast. I think a
succinct option less prone to naive error in assumptions about how Float
values are compared is less likely to be passed over as unnecessary by
someone who doesn't really grasp the differences in favor of the more
succinct Float#==.
4. As you say, good documentation helps. Part of this, of course, is a
succinct and clear description in the ri documentation for the alternate
comparison method(s) for how it/they differ(s) from Float#==, but part of
it would also be mentioning something about the potential problems of
Float#== in its ri documentation as well. In fact, I think adding
something like that to the Float#== documentation regardless of whether
any additional comparison methods are ever added would be a good idea, if
only to reduce the volume of complaints that Ruy can't do math by getting
people who notice subtle precision bugs in their programs to realize that
a work-around is needed.
5. I think I had a point 5 in mind, but I've forgotten it.
>
> Can someone please explain to me in clear, direct terms why there is
> opposition to the simple expedient of adding at least one additional
> comparison method to Float to address the vast majority of casual use
> cases without having to resort to reimplementing such comparison methods
> over and over again or bringing in additional libraries with onerous
> syntactic verbosity?There is no method which does not require an additional data item
(what I called "level of imprecision"), what makes it harder to make a
proper choice. See also:
http://www.ruby-forum.com/topic/3481172#1042651
That argument seems, to me, to only really dispute the suggestion that it
might be better to replace Float#==, and not the suggestion to supplement
it with something well-defined. The goal I wish to reach here is not to
have something as "standard" and self-evidently correct on an engineering
level as Float#==, but to have something that solves 98% of the problem
for 98% of casual use cases, well-defined so that people who use it know
what to expect. When I say "know what to expect", of course, I'm not
talking about knowing that it fails sometimes to meet the expectations of
decimal arithmetic; I'm talking about rules for how it works that are
clear and simple enough that anyone who reads a succinct description of
the method should never get a result that produces exactly what the
person expects. This means ensuring it's easy to figure out to what
level of precision it *consistently* matches the expectations of how the
comparison would work with an actual decimal implementation behind the
number rather than a binary implementation.
That may even be a sliding scale, as in the case of a ratio comparison,
as long as the rules for how it slides are simple, clear, and easy to
evaluate in one's head while writing code.
···
On Tue, Jan 31, 2012 at 05:47:08PM +0900, Robert Klemme wrote:
On Mon, Jan 30, 2012 at 6:27 PM, Chad Perrin wrote:
--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
Unless the parser stores a string representation in addition to the
float itself. Then at runtime, the string rep could be used to
instantiate a BigDecimal if that runtime option has been activated.
Of course, I'm NOT advocating this!
···
On Tue, Jan 24, 2012 at 10:19 AM, Ryan Davis <ryand-ruby@zenspider.com> wrote:
> Ruby is pretty dynamic. It seems entirely reasonable to consider whether
> Float could be redefined to dispatch to BigDecimal except when called
> explicitly, for instance.Except for that whole "parse time is different from run time" part you
seem to be blithely ignoring. If you've already parsed float literals, then
they're floats and are already lossy.
Are you telling me that 1.1 is automatically lossy, regardless of how you
got there? I guess I need to go back and refresh my understanding of the
math, because I thought a literal decimal number was fine but one
achieved by arithmetic was likely to contain subtle errors due to the way
the binary math is handled.
···
On Tue, Jan 24, 2012 at 08:19:06AM +0900, Ryan Davis wrote:
Except for that whole "parse time is different from run time" part you
seem to be blithely ignoring. If you've already parsed float literals,
then they're floats and are already lossy.
--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]