Ruby and modifying self for a Float instance - ruby

I would like to change the self value of a float instance.
I have the following method :
class Float
def round_by(precision)
(self * 10 ** precision).round.to_f / 10 ** precision
end
end
And I would like to add the round_by! method which will modify the self value.
class Float
def round_by!(precision)
self = self.round_by(precision)
end
end
But I got an error saying I can't change the value of self.
Any idea ?

You can't change the value of self. It always points to the current object, you can't make it point to something else.
When you want to mutate the value of an object, you either do this by calling other mutating methods or setting or changing the values of instance variables, not by trying to reassign self. However in this case, that won't help you, because Float doesn't have any mutating methods, and setting instance variables won't buy you anything, because none of the default float operations are affected by any instance variables.
So the bottom line is: you can't write mutating methods on floats, at least not in the way you want.

You can also create a class and store the float in a instance variable:
class Variable
def initialize value = nil
#value = value
end
attr_accessor :value
def method_missing *args, &blk
#value.send(*args, &blk)
end
def to_s
#value.to_s
end
def round_by(precision)
(#value * 10 ** precision).round.to_f / 10 ** precision
end
def round_by!(precision)
#value = round_by precision
end
end
a = Variable.new 3.141592653
puts a #=> 3.141592653
a.round_by! 4
puts a #=> 3.1416
More about using "class Variable" here.

This is actually a really good question and I'm sorry to say that you can't - at least not with the Float class. It's immutable. My suggestion would be to create your own class the implements Float (aka inherits all the methods), like so in pseudo-code
class MyFloat < Float
static CURRENT_FLOAT
def do_something
CURRENT_FLOAT = (a new float with modifications)
end
end

Related

Definied Anonymous class in rspec won't respond to new

so I have the following anonymous class definition:
let!(:fake_class) do
Class.new(Integer) do
def initialize(value)
#value = value
end
def ==(other)
#value == other
end
def coerce(other)
[#value, other]
end
def to_s
#value.to_s
end
end
end
But when I do:
fake_class.new 4
I just get undefined method 'new' for #<Class:0x00007fc065377c88>
I've tried doing
define_method :initialize do |value|
#value = value
end
no difference
the only way it responds to new is if I do
class << self
def new(value)
#value = value
end
end
but that obviously won' work as I need it to act like a real class.
Why do I see lots of tutorials using intialize and it working as expected yet it doesn't seem to work for me? Is it becuase i'm defining it in rspec or somthing?
The issue here is nothing to do with rspec, nor anonymous classes.
The problem is that in ruby, you cannot subclass Integer*.
Ruby stores small Integers (formerly known as Fixnums) as immediate values, using some of the low bits of the word to tag it as such, instead of a pointer to an object on the heap. Because of that, you can't add methods to a single "instance" of Integer, and you can't subclass it.
If you really want an "Integer-like" class, you could construct a workaround with a class that has an integer instance variable, and forward method calls appropriately:
class FakeInteger
def initialize(integer)
#integer = integer
end
def method_missing(name, *args, &blk)
ret = #integer.send(name, *args, &blk)
ret.is_a?(Numeric) ? FakeInteger.new(ret) : ret
end
end
* Technically you can, but since you cannot instantiate any objects from it, it's pretty useless :)
Your code is correct but Integer does not respond to .new and so your child class will also not respond to .new.
irb(main):001:0> Integer.new
NoMethodError (undefined method `new' for Integer:Class)
When you call Integer(123) you actually call a global function defined here:
https://github.com/ruby/ruby/blob/v2_5_1/object.c#L3987
https://github.com/ruby/ruby/blob/v2_5_1/object.c#L3178

Dynamic Variables to access class methods in Ruby

Working in Ruby, we have to use a 3rd party Framework, which has a class setup something like this:
class Foo
attr_accessor :bar
def initialize()
end
end
class Poorly_Designed_Class
attr_accessor :thing1
attr_accessor :thing2
attr_accessor :thing3
attr_accessor :thing4
attr_accessor :thing5
# through :thing_n .. number defined at runtime
def initialize()
#thing1 = Foo.new
#thing2 = Foo.new
#thing3 = Foo.new
#thing4 = Foo.new
#thing5 = Foo.new
end
end
I don't know how many "things" there are until run time. there could be 5 or their could be 50.
What I would like to do is something like:
pdc = Poorly_Designed_Class.new
for i in 0..numberOfThings do
pdc."thing#{i}".bar = value[i]
end
The above doesn't work.
I've also tried accessing it via:
instance_variable_set("pdc.thing#{i}.bar",value)
I understand that the class should be using an array or hash. Unfortunately I can't do anything about how the class is designed and we have to use it.
Is what i'm trying to do even possible?
You could either try to call the getter (preferably, since it honors encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things.times do |i|
pdc.public_send(:"thing#{i}").bar = value[i]
end
or get the instance variable (less preferred, since it breaks encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things) do |i|
pdc.instance_variable_get(:"#thing#{i}").bar = value[i]
end
So, you were on the right track, there were just two problems with your code: instance variable names start with an # sign, and . is not a legal character in an identifier.
You're using Object#instance_variable_set incorrectly. The first argument must be a string or a symbol representing the name of an instance variable including the # prefix: e.g. "#thing{i}". However you actually want to get the value of an instance variable and then send #bar= to it. That can be done with Object#instance_variable_get:
1.upto(numberOfThings) { |i| pdc.instance_variable_get("#thing#{i}").bar = value[i] }
That's a bit long and since attr_acessor :thingX defines getter methods, it's usually preferable to call them with Object#public_send instead of directly accessing the instance variable (a getter method might do something else than just returning a value):
1.upto(numberOfThings) { |i| pdc.public_send("thing#{i}").bar = value[i] }

Ruby Instance Methods and Variables

There is something that i don't understand about ruby class instance variable or methods**.
So i have this code that keeps on giving me this error and i cant understand
Looks ruby thinks that i am trying to call for Float.in_celsius but I want to make this call within my class instance.
#-----------------------------------
def ftoc(fr)
fr = fr.to_f
if (fr == 32)
c = 0
elsif (fr == 212)
c = 100
else
c = (fr-32.0)*(5.0/9.0)
end
return c
end
def ctof (cl)
cl = cl.to_f
f = (cl*(9.0/5.0))+32.0
return f
end
#-----------------------------------
class Temperature
attr_accessor :in_celsius, :in_fahrenheit
#class metods
def self.from_celsius(cel)
puts "from celsious\n"
puts "cel: #{cel}\n"
#in_fahrenheit = cel
#in_celsius = ctof(cel)
puts "==============================\n"
return #in_celsius
end
def self.in_celsius
#in_celsius
end
end
puts "==============================\n"
puts Temperature.from_celsius(50).in_celsius
puts Temperature.from_celsius(50).in_fahrenheit
and Error is
test.rb:54: in '<main>' : undefined method 'in_celsius' for 122.0:float (noMethod Error)
enter code here
You have a fundamental misunderstanding of how classes work in Ruby. Right now all of your variables and methods are defined at class level. That means that everything you do in the methods is acting directly on the class itself. Instead, you should create instances of Temperature.
class Temperature
# special method called when creating a new instance
def initialize celsius
#in_celsius = celsius
#in_fahrenheit = celsius * 9 / 5.0 + 32
end
def self.from_celsius celsius
new celsius # built in method to create an instance, passes argument to initialize
end
# we defined initialize using celsius, so here we must convert
def self.from_fahrenheit fahrenheit
new((fahrenheit - 32) * 5 / 9.0)
end
private_class_method :new # people must use from_celsius or from_fahrenheit
# make instance variables readable outside the class
attr_accessor :in_celsius, :in_fahrenheit
end
Temperature.from_celsius(50).in_celsius
This code isn't perfect (from_fahrenheit does a redundant conversion) but it should give you the idea of how to redesign your class.
from_celsius is returning a float which doesn't have an in_celsius method. You need it to return an instance of Temperature which would have that method.
Got to say your intent is a tad confusing, unless you have some other uses for the class Temperature, so it's bit hard to say which way you should go.
Let's see the code puts Temperature.from_celsius(50).in_celsius in details:
Call to singleton method ::from_celsius of Temperature class. That is ok (with some stranges), and t returns as instance of Float class because of result of #ctof method.
Call to instance method #in_celsius of object, which is returned from the previous method. Since it was the Float, the ruby interpreter searches for its instance method #in_celsius, hasn't find it out, and throws the NoMethodError exception.
How to fix.
Since you treat ::from_celsius as a constructor of Temperature class, I believe, that you shell to pass the floating value into the new method, and return created object. You will have class code as follows:
def initialize( value )
#in_fahrenheit = cel
#in_celsius = ctof(cel)
end
def self.from_celsius(cel)
puts "from celsious\n"
puts "cel: #{cel}\n"
temp = Temperature.new( cel )
puts "==============================\n"
return temp
end

How to overload contructor in Ruby? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
In Ruby is there a way to overload the initialize constructor?
BigDecimal does not take a float as in initial value, so I am writing a constructor to handle it. It seems to be ignoring the initialize method and calling the default constructor.
It throws TypeError can't convert Float into String (TypeError)
The method format does work.
file BigDecimal.rb:
require 'bigdecimal'
class BigDecimal
def initialize
if self.class == Float
super self.to_s
end
end
def format
sprintf("%.2f", self)
end
end
Then, in file test.rb:
require 'BigDecimal' # => true
bd = BigDecimal.new('54.4477') # works
puts bd.format # 54.44
bd2 = BigDecimal.new(34.343) # TypeError: can't convert Float into String (TypeError)
Ruby 1.9.2
Problems with your code:
You use a monkey patch instead of inheriting, so in your initialize method, super will call the initialize method of Object, which is the super class of BigDecimal. To call the default constructor, you have to use some other method as showing below.
You did not put arguments for the initialize method.
BigDecimal DOES take float as constructor argument
Therefore,
You can use directly the default constructor and pass a float as:
BigDecimal.new(34.343, 5) # 5 is the precision
You can override the constructor in this way:
NOTE: we usually alias initialize method. However in this case this does not seem to work (for some unknown reason that initialize does not get called)... So we have to alias new method which is more fundamental.
require 'bigdecimal'
class BigDecimal
class << self
alias_method :__new__, :new #alias the original constructor so we can call later
def new(*args)
if args.length == 1 && args[0].is_a?(Float)
__new__(args[0].to_s)
else
__new__(*args)
end
end
end
def format
sprintf("%.2f", self)
end
end
BigDecimal.new(12.334)
#<BigDecimal:10a9a48,'0.12334E2',18(18)>
BigDecimal does not take a float as in initial value
Are you sure?
BigDecimal.new(2.4)
#=>ArgumentError: can't omit precision for a Rational.
So you have to give a precision as second argument:
BigDecimal.new(2.4, 2)
#=> #<BigDecimal:7ff689b0f2e8,'0.24E1',18(36)>
The docs don't indicate a change between 1.9.2 and 1.9.3.

Sub-classing Fixnum in ruby

So I understand you aren't supposed to to directly subclass Fixnum, Float or Integer, as they don't have a #new method. Using DelegateClass seems to work though, but is it the best way? Anyone know what the reason behind these classes not having #new is?
I need a class which behaves like a Fixnum, but has some extra methods, and I'd like to be able to refer to its value through self from within the class, for example:
class Foo < Fixnum
def initialize value
super value
end
def increment
self + 1
end
end
Foo.new(5).increment + 4 # => 10
You can pretty easily set up a quick forwarding implementation yourself:
class MyNum
def initialize(number)
#number = number
end
def method_missing(name, *args, &blk)
ret = #number.send(name, *args, &blk)
ret.is_a?(Numeric) ? MyNum.new(ret) : ret
end
end
Then you can add whatever methods you want on MyNum, but you'll need to operate on #number in those methods, rather than being able to call super directly.
IIRC, the main implementation of Ruby stores Fixnums as immediate values, using some of the low bits of the word to tag it as a Fixnum instead of a pointer to an object on the heap. That's why, on a 32-bit machine, Fixnums are only 29-bits (or whatever it is) instead of a full word.
So because of that, you can't add methods to a single "instance" of Fixnum, and you can't subclass it.
If you're dead-set on having a "Fixnum-like" class, you'll probably have to make a class that has a Fixnum instance variable, and forward method calls appropriately.
Could you not extend FixNum itself? Like...
class Fixnum
def even?
self % 2 == 0
end
end
42.even?

Resources