Why doesn't Float() work the same as Integer()?

M

Michael W. Ryder

In my continuing work learning Ruby while creating a Rational class I
ran into a problem with converting Strings. I am allowing Strings as
input to the .new method and converting them to a Float, Integer, or
Rational before continuing. At first I was just using Float(x) and
later converting the Float to an Integer. This works fine unless I try
something like Float("0x11") which crashes. Integer("0x11") returns 17
as expected. Currently I have to try to convert to Float and if that
fails try to convert to Integer which seems to be more work than it
needs to be.
If I use Float("17") it returns 17.0 and Float(0x11) returns 17.0, both
as expected. Why doesn't Float("0x11") work? As Ruby doesn't appear to
handle Floats of bases other than 10 (or at least I haven't figured out
how to yet) I can see it choking on Float("0x11.34") but it shouldn't
choke on Float("0x11").
Part of what I am doing is trying to follow the statement in Pickaxe
about duck typing and programming for any input, not just a select subset.
 
R

Rimantas Liubertas

If I use Float("17") it returns 17.0 and Float(0x11) returns 17.0, both as
expected.

Yes, because Kernel.Float knows how to convert string "17" to float
and number 17
written as 0x11 to float.
Why doesn't Float("0x11") work?

You answer that yourself. Unlike Kernel.Integer, Kernel.Float does not honor
radix indicators (0, 0b, 0x), thus string '0x11' is invalid for that method.
While '0x11'.to_f also does not know how to deal with this, it
silently returns 0.0.
Kernel.Float is different in that way, that it rises an exception when
given string is invalid.
As Ruby doesn't appear to handle
Floats of bases other than 10 (or at least I haven't figured out how to yet)
I can see it choking on Float("0x11.34") but it shouldn't choke on
Float("0x11").

It should. Because this method does not know anything about 0x prefix
the whole string is invalid
to it.
Kernel.Integer KNOWS how to deal with prefixes, so it handles ocatal,
binary and hex representations
in string format just fine.
<...>

Regards,
Rimantas
 
M

Michael W. Ryder

Rimantas said:
Yes, because Kernel.Float knows how to convert string "17" to float
and number 17
written as 0x11 to float.


You answer that yourself. Unlike Kernel.Integer, Kernel.Float does not honor
radix indicators (0, 0b, 0x), thus string '0x11' is invalid for that method.
While '0x11'.to_f also does not know how to deal with this, it
silently returns 0.0.
Kernel.Float is different in that way, that it rises an exception when
given string is invalid.


It should. Because this method does not know anything about 0x prefix
the whole string is invalid
to it.
Kernel.Integer KNOWS how to deal with prefixes, so it handles ocatal,
binary and hex representations
in string format just fine.
<...>

My question is why doesn't Float know how to deal with prefixes such as
Hex? I wouldn't care if it tossed out invalid hex numbers such as 0x1h
but it should at least try. As far as I am concerned the only
difference between Float and Integer should be the return type, not what
types they can handle. Integer has no problems with any valid Float or
string representation of a Float, so Float should be able to handle any
Integer or string representation of a valid Integer.
I could probably create a different version of Float or to_f for my
class that handles string values like 0x11 but would rather not as it
may confuse someone in the future if they didn't know that it was
different than the built-in methods.
 
E

Eric I.

My question is why doesn't Float know how to deal with prefixes such as
Hex?  I wouldn't care if it tossed out invalid hex numbers such as 0x1h
but it should at least try.  As far as I am concerned the only
difference between Float and Integer should be the return type, not what
types they can handle.

Well you're asking "why", and I suppose you'd have to address the
question to the core Ruby team. However, I can venture a guess, for
whatever that might be worth. And there are three elements to my
guess.

First, Float() and Integer() work like Ruby itself. In Ruby code you
can hardcode integer literals using 17 (dec), 021 (oct), or 0x11
(hex). But you can only hardcode floating point literals using base
ten. Admittedly, that's a bit of a punt since you'll probably ask why
Ruby works that way.

Second, I've never had the need to represent a floating point value as
a hexadecimal literal or string. With integers you sometimes need bit
masks and such. Perhaps there are domains where this would be very
useful. But I imagine most programmers don't typically run into
them. And like anything, people are more likely to do things that
they believe will be useful, or they scratch their own itches.

And my third reason is that there might be ambiguity in how to
interpret such a string. The representation of floating point values
as a bit pattern is typically done using IEEE 754. So, if we wanted
to represent a PI as a floating point double as a hexadecimal
constant, I think it would be "0x400921fb54442d18". However that
could also be interpreted as the integer 4,614,256,656,552,045,848.

Given your original post, you seem to be thinking that hexadecimal
constants would include a "." to separate the whole part from the
fractional part. In that case, I imagine pi would be represented as
"0x3.243f6a8885a3".

But for those programmers out there who'd want some type of
functionality, they may not be unified in their preference for the
format -- IEEE 754 or with an included decimal point.

I don't know how satisfying you'll find those answers, but they're the
best I can do.

By the way, if you want to include such functionality in your Rational
project, the following Ruby snippets might be helpful.

To figure out how pi would be represented as an IEEE 754 encoded
value, I used the following Ruby code:

"0x" + [Math::pI].pack("G").unpack("H*").first

If you're unfamiliar with the methods, you may be interested in
reading the documentation on Array#pack and String#unpack.

To figure out how pi would be represented in the other form, I used
this Ruby code:

fractional_hex_digits = 12 # how many hex digits right of decimal
"0x" +
Math::pI.floor.to_s(16) +
"." +
((Math::pI - Math::pI.floor) *
16**fractional_hex_digits).floor.to_s(16)

If you plan on using any of this code, please check my work since it
comes with absolutely to warranties.

Best,

Eric

====

LearnRuby.com offers Rails & Ruby HANDS-ON public & ON-SITE
workshops.
Ruby Fundamentals Wkshp June 16-18 Ann Arbor, Mich.
Ready for Rails Ruby Wkshp June 23-24 Ann Arbor, Mich.
Ruby on Rails Wkshp June 25-27 Ann Arbor, Mich.
Ruby Plus Rails Combo Wkshp June 23-27 Ann Arbor, Mich
Please visit http://LearnRuby.com for all the details.
 
M

Michael W. Ryder

Eric said:
Well you're asking "why", and I suppose you'd have to address the
question to the core Ruby team. However, I can venture a guess, for
whatever that might be worth. And there are three elements to my
guess.

First, Float() and Integer() work like Ruby itself. In Ruby code you
can hardcode integer literals using 17 (dec), 021 (oct), or 0x11
(hex). But you can only hardcode floating point literals using base
ten. Admittedly, that's a bit of a punt since you'll probably ask why
Ruby works that way.

Second, I've never had the need to represent a floating point value as
a hexadecimal literal or string. With integers you sometimes need bit
masks and such. Perhaps there are domains where this would be very
useful. But I imagine most programmers don't typically run into
them. And like anything, people are more likely to do things that
they believe will be useful, or they scratch their own itches.

I think my main concern was this was yet another "gotcha" that only came
up if you tested for it. My original conversion for strings to floats
was a simple: x = Float(x) if (Float(x) rescue false). This works fine
for "12", and "12.34" but crashes if I use "0x11" or any other hex
value, something Integer does not. What I ended up having to do is:

temp = nil
temp = Float(x) if (Float(x) rescue false)
if temp == nil
temp = Integer(x) if (Integer(x) rescue false)
end
if temp == nil
x = 0
else
x = temp
end

As you can see this is much more code, and that is before I add in more
code for error handling.

And my third reason is that there might be ambiguity in how to
interpret such a string. The representation of floating point values
as a bit pattern is typically done using IEEE 754. So, if we wanted
to represent a PI as a floating point double as a hexadecimal
constant, I think it would be "0x400921fb54442d18". However that
could also be interpreted as the integer 4,614,256,656,552,045,848.

Given your original post, you seem to be thinking that hexadecimal
constants would include a "." to separate the whole part from the
fractional part. In that case, I imagine pi would be represented as
"0x3.243f6a8885a3".
Actually I wouldn't care if it couldn't handle floating point numbers in
any base except 10 as it doesn't appear to be able to do so in other
methods like sprintf or printf. I don't think it would have been that
hard to allow Float to handle strings like '0x11' as the code has
already been created for Integer.
But for those programmers out there who'd want some type of
functionality, they may not be unified in their preference for the
format -- IEEE 754 or with an included decimal point.

I don't know how satisfying you'll find those answers, but they're the
best I can do.

I have no problem with your replies and can see that maybe the original
implementation was made to work correctly for a small subset of possible
values rather than try to handle everything with possible problems. As
I mentioned earlier I was trying to take the section on duck typing
seriously and make my class handle as many reasonable inputs as
possible. I will probably try to include arrays and strings like "17 4"
as inputs once I finish the inputs for Integers, Floats, Rationals, and
Strings in any combination.
I hadn't thought about adding the ability to display Rational numbers in
other bases but that should be trivial, at least compared to Floating
point numbers. I would use something like 0x11/0x4 to represent 17/4.
Since the numerator and denominator of my Rational numbers are always
Integers converting should be easy.

By the way, if you want to include such functionality in your Rational
project, the following Ruby snippets might be helpful.

To figure out how pi would be represented as an IEEE 754 encoded
value, I used the following Ruby code:

"0x" + [Math::pI].pack("G").unpack("H*").first

If you're unfamiliar with the methods, you may be interested in
reading the documentation on Array#pack and String#unpack.

To figure out how pi would be represented in the other form, I used
this Ruby code:

fractional_hex_digits = 12 # how many hex digits right of decimal
"0x" +
Math::pI.floor.to_s(16) +
"." +
((Math::pI - Math::pI.floor) *
16**fractional_hex_digits).floor.to_s(16)

If you plan on using any of this code, please check my work since it
comes with absolutely to warranties.

Best,

Eric

====

LearnRuby.com offers Rails & Ruby HANDS-ON public & ON-SITE
workshops.
Ruby Fundamentals Wkshp June 16-18 Ann Arbor, Mich.
Ready for Rails Ruby Wkshp June 23-24 Ann Arbor, Mich.
Ruby on Rails Wkshp June 25-27 Ann Arbor, Mich.
Ruby Plus Rails Combo Wkshp June 23-27 Ann Arbor, Mich
Please visit http://LearnRuby.com for all the details.
 
A

ara.t.howard

temp = nil
temp = Float(x) if (Float(x) rescue false)
if temp == nil
temp = Integer(x) if (Integer(x) rescue false)
end
if temp == nil
x = 0
else
x = temp
end

As you can see this is much more code, and that is before I add in
more code for error handling.

sort of, you can write this

x = Float(x) rescue Integer(x) rescue 0

but be aware, Float and Integer will handle a nil argument differently.



a @ http://codeforpeople.com/
 
M

Michael W. Ryder

ara.t.howard said:
sort of, you can write this

x = Float(x) rescue Integer(x) rescue 0

This works fine for me and returns values that I am expecting. Changing
it slightly to:
x = (Float(x) rescue Integer(x) rescue 0).to_f
gives me the Float value I was looking for. I can live with this, just
part of learning a new language is learning when something will not work
the way you expect and how to work around it.
Thank you for the code. I am still learning how to use exceptions.
 
S

Student Jr

Michael said:
In my continuing work learning Ruby while creating a Rational class I
ran into a problem with converting Strings. I am allowing Strings as
input to the .new method and converting them to a Float, Integer, or
Rational before continuing. At first I was just using Float(x) and
later converting the Float to an Integer. This works fine unless I try
something like Float("0x11") which crashes. Integer("0x11") returns 17
as expected. Currently I have to try to convert to Float and if that
fails try to convert to Integer which seems to be more work than it
needs to be.
If I use Float("17") it returns 17.0 and Float(0x11) returns 17.0, both
as expected. Why doesn't Float("0x11") work? As Ruby doesn't appear to
handle Floats of bases other than 10 (or at least I haven't figured out
how to yet) I can see it choking on Float("0x11.34") but it shouldn't
choke on Float("0x11").
Part of what I am doing is trying to follow the statement in Pickaxe
about duck typing and programming for any input, not just a select
subset.

As someone who has done floating point validation on microprocessors, I
can unequivocally state that the only "alternate base" of interest is to
represent the byte string as nibbles or bits. That would be
'400000000000000000' => 2.0 in EP, '40000000000000000' => 2.0 in DP,
'400000000' => in SP. Which means of course that
'0000000040000000'.to_f != '40000000'.to_f. These are formats for IEEE
754. Note that the internal representation of extended precision
numbers has more bits. The Athlon had 91. Eariler cpus almost
certainly had at least 83. There are very, VERY few people working in
an environment where this matters, and most are good enough to write
their own code as needed.

The hex and binary options for specifying integers exist because we
often need to look at integers as bit strings. Oct is a historical
anomaly. Different classes have different behaviors because they are
different. The fact that Float and Int happen to share the base class
Numeric is of no concern to either.
 
M

Mark Hubbart

Michael W. Ryder wrote:
[snip]
If I use Float("17") it returns 17.0 and Float(0x11) returns 17.0, both
as expected. Why doesn't Float("0x11") work? As Ruby doesn't appear to
handle Floats of bases other than 10 (or at least I haven't figured out
how to yet) I can see it choking on Float("0x11.34") but it shouldn't
choke on Float("0x11").

Actually, this is probably the most logical behavior. Integer() and
Float() have the same defined behavior; they both can accept one of two
inputs:
1. A Numeric that can be converted into the correct type;
2. A String that can be parsed into the correct type.

If a string is provided, then it must follow the same rules as the
appropriate literal representation.... This is the source of the
differences. So:
To make them truly cross-compatible would eliminate the error-checking.
Part of what I am doing is trying to follow the statement in Pickaxe
about duck typing and programming for any input, not just a select
subset.

Beware of doing *too much* duck typing. Figure out a reasonable point to
stop. Unless there's a significant amount of value added by allowing
strings, I wouldn't do it. If you can think of a reasonable circumstance
where the programmer would want to pass a string instead of an actual
number, go ahead; otherwise, it' just bloat, and bloat is a great source
for bugs :)
 
M

Michael W. Ryder

Mark said:
Michael W. Ryder wrote:
[snip]
If I use Float("17") it returns 17.0 and Float(0x11) returns 17.0, both
as expected. Why doesn't Float("0x11") work? As Ruby doesn't appear to
handle Floats of bases other than 10 (or at least I haven't figured out
how to yet) I can see it choking on Float("0x11.34") but it shouldn't
choke on Float("0x11").

Actually, this is probably the most logical behavior. Integer() and
Float() have the same defined behavior; they both can accept one of two
inputs:
1. A Numeric that can be converted into the correct type;
2. A String that can be parsed into the correct type.

If a string is provided, then it must follow the same rules as the
appropriate literal representation.... This is the source of the
differences. So:
To make them truly cross-compatible would eliminate the error-checking.

I am glad to see that I am not the only one that feels this way.

Beware of doing *too much* duck typing. Figure out a reasonable point to
stop. Unless there's a significant amount of value added by allowing
strings, I wouldn't do it. If you can think of a reasonable circumstance
where the programmer would want to pass a string instead of an actual
number, go ahead; otherwise, it' just bloat, and bloat is a great source
for bugs :)

One of the nice things about Ruby is that a lot of the code necessary to
cast a variable to another type is already there so for the most part it
was very simple to add support for additional types of input. Along the
way I learned about other things like handling exceptions, and hunting
down some of the "bugs" in converting inputs is where I found some
things that I feel are inconsistencies. Since this project is more for
my benefit, as there already exists a rational module, I am not too
worried about code bloat except when it occurs to handle unexpected
behavior in the language.
 
S

Sebastian Hungerecker

Mark said:
If a string is provided, then it must follow the same rules as the
appropriate literal representation.... This is the source of the

differences. So:

But shouldn't Float("023") return an error in this case? After all "023" is=
=20
not a literal representation of a float (neither is "23").

=2D-=20
Jabber: (e-mail address removed)
ICQ: 205544826
 
M

Mark Wilden

But shouldn't Float("023") return an error in this case? After all
"023" is
not a literal representation of a float (neither is "23").

Again, I think it comes down to pragmatism. It's not what these
methods "should" return; it's what it's most -useful- for them to
return.

///ark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,586
Members
45,089
Latest member
Ketologenic

Latest Threads

Top