Microrant on Ruy's Math Skills

On Mon, Jan 30, 2012 at 5:52 PM, Tony A. [email protected]
wrote:

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.

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.

Well, you only convert during reading from and writing to the
database. The rest of the software works with instances of a proper
class which handles all the nifty details internally. That’s the
whole point of OO, isn’t it?

Cheers

robert

On Mon, Jan 30, 2012 at 05:22:47PM +0900, Robert K. wrote:

On Mon, Jan 30, 2012 at 06:01:02PM +0900, Florian G. wrote:

Floating points are a great choice for approximating continuous values and
cases.

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 Tue, Jan 31, 2012 at 1:27 AM, Chad P. [email protected] wrote:


Chad P. [ original content licensed OWL: http://owl.apotheon.org ]

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 11:35:10AM +0900, Yong Li wrote:

syntactic verbosity?
b = 1.1
a.approx_equal(b) #=> true
(a * 100).approx_equal(b * 100) #=> false

I guess, this again breaks the simple math.

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 4:03 PM, Chad P. [email protected] wrote:

over and over again or bringing in additional libraries with onerous
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.

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?

suppose we are writing a unit test method
def test_something() do
a = pre_condition() # => a is a Float
assert_approx_equal a, expected1 # => ok
a = process a # => do the real
work
assert_approx_equal a, expected2 # => the assertion may fail, if
expected 2 is evaluated in my head
end

do you consider this as a surprising result that may happen a lot?

On Mon, Jan 30, 2012 at 6:27 PM, Chad P. [email protected] wrote:

On Mon, Jan 30, 2012 at 05:22:47PM +0900, Robert K. wrote:

On Mon, Jan 30, 2012 at 6:56 AM, Chad P. [email protected] wrote:

On Mon, Jan 30, 2012 at 10:03:04AM +0900, Gary W. wrote:

On Jan 29, 2012, at 2:26 AM, Jon L. wrote:

On Jan 27, 2012, at 3:26 PM, Gary W. wrote:

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 - 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?

That’s not what I suggested. I was talking about “precise math” -
what you find in computer algebra systems.

everyone to create his or her own. I really, really don’t understand
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. :slight_smile:

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

Cheers

robert

On Tue, Jan 31, 2012 at 4:03 PM, Chad P. [email protected] wrote:

Good Chad,
There is nothing wrong with your suggestion, as there is nothing wrong
for many people saying it won’t help much.
It’s just different opinions, and I treat them as food for thoughts.
Reading this long conversation has led me to think about problems I
never imagined, and that’s a good thing for me.

I was not trying to say you are wrong. In fact, your voice is
necessary to remind me that Float is imperfect, and we should improve
it. I was just trying to give more opinions.

As for my little contrived example, if it is worthless for you, I am
sorry for wasting your time reading it.

On Tue, Jan 31, 2012 at 06:35:29PM +0900, Yong Li wrote:

a = process a # => do the real work
assert_approx_equal a, expected2 # => the assertion may fail, if
expected 2 is evaluated in my head
end

do you consider this as a surprising result that may happen a lot?

Yeah, basically. Of course, “a lot” is relative – but the upshot is
that it happens enough to be a problem when offering an additional
comparison method should yield something much easier to evaluate in
one’s
head.

Of course, I don’t know how you get a unit test to make use of your
brain’s arithmetic capabilities. . . .

On Tue, Jan 31, 2012 at 05:47:08PM +0900, Robert K. wrote:

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.)

On Wed, Feb 01, 2012 at 10:31:51AM +0900, Yong Li wrote:

process(Fixnum i) is supposed to return 100.0 * i

and I am going to test it

assert_with_approx_equal( process(1.1), 110.0)

=> oops, this fails, but I thought 100.0 * 1.1 == 110.0

that ‘110.0’ Float literal is calculated by my brain without using
computer, and I may naively claim that the process method is wrong,
but actual it is this unit test which is wrong.

I understood that part.

does unit test qualify your “98% of the problem for 98% of casual use cases”?

I didn’t understand that part.

On Wed, Feb 1, 2012 at 12:49 AM, Chad P. [email protected] wrote:

assert_approx_equal a, expected1 # => ok
head.

Of course, I don’t know how you get a unit test to make use of your
brain’s arithmetic capabilities. . . .

Sorry, my bad for confusing you. Second try:

process(Fixnum i) is supposed to return 100.0 * i

and I am going to test it

assert_with_approx_equal( process(1.1), 110.0)

=> oops, this fails, but I thought 100.0 * 1.1 == 110.0

that ‘110.0’ Float literal is calculated by my brain without using
computer, and I may naively claim that the process method is wrong,
but actual it is this unit test which is wrong.

does unit test qualify your “98% of the problem for 98% of casual use
cases”?

On Wed, Feb 1, 2012 at 4:23 AM, Chad P. [email protected] wrote:

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.

Excellent points, well written.

Gavin

does unit test qualify your “98% of the problem for 98% of casual use cases”?

I didn’t understand that part.

Then, just ignore it. I admit that this comment give no real value and
won’t benefit any one.

it’s stuff like this that suggest that our interpreter needs to suggest
possible, likely outcomes to executed malformed code, and try to find a
way to parse it, or scavenge it for memetic information about possible
code execution protocols. Most people are still coding for one core. You
need to give your cores names and get them to talk to each other. I
don’t have the information or computer science degree to do so. Oh well,
at least I have my positional numbering system.

1.1 + -1.to_f = 1.0, with a radix of |Q|
binary isn’t required if you use excess-127

Gary W. wrote in post #1041917:

On Jan 21, 2012, at 9:06 AM, Intransition wrote:

So simple…

1.1 - 1.to_f == 0.1

false

(rumble) (rumble) Pathetic!

Decimal literals (e.g. 1.1) can’t always be represented exactly as
binary floats: