njh / ruby-mqtt

Pure Ruby gem that implements the MQTT protocol, a lightweight protocol for publish/subscribe messaging.
http://www.rubydoc.info/gems/mqtt
MIT License
541 stars 135 forks source link

ruby-mqtt does not work with mathn library. #79

Closed yoggy closed 8 years ago

yoggy commented 8 years ago

see also... https://gist.github.com/yoggy/aa77faba0a0840ef0faf2182d23a665c

njh commented 8 years ago

Hi,

Sorry, I don't quite understand this. Why does the mathn library change the behaviour of the / operator? What is the different between / and .div?

It looks like mathn is part of the standard ruby build? Would it be possible to write a test that verifies this behaviour?

nick.

yoggy commented 8 years ago

Hi njh,

I am interested in this strange behavior.

Do you know "Rational" class? Rational is defined when you require mathn.

Rational overrides Numeric#/ method. If Rational is defined, Fixnum#/ method returns a Rational number instead of a Fixnum.

$ irb
irb(main):001:0> 1000 / 123
=> 8
irb(main):002:0> (1000 / 123).class
=> Fixnum
irb(main):003:0> require 'mathn'
=> true
irb(main):004:0> 1000 / 123
=> (1000/123)     #### Fixnum#/ returns Rational number!! ####
irb(main):005:0> (1000 / 123).class
=> Rational

Rational does not overide Numeric#div method. If you expect [Fixnum / Fixnum => Fixnum], you should use Fixnum#div method.

$ irb
irb(main):001:0> require 'mathn'
=> true
irb(main):002:0> 1000.div(128)
=> 7
irb(main):003:0> 1000.div(128).class
=> Fixnum

In this case, if require 'mathn'...

Step 1

                    .
                    .
          #### body_length is assumed to 11. ####
                    .
                    .
194       # Build up the body length field bytes
195       begin
196         digit = (body_length % 128)
197         body_length = (body_length / 128)  #### (1) body_length is Rational number (11/128) [Fixnum / Fixnum => Rational]     ####
198         # ....
199         digit |= 0x80 if (body_length > 0)
200         header.push(digit)
201       end while (body_length > 0)

Step 2

194       # Build up the body length field bytes
195       begin
196         digit = (body_length % 128)
197         body_length = (body_length / 128)
198         # ....
199         digit |= 0x80 if (body_length > 0)
200         header.push(digit)
201       end while (body_length > 0)          #### (2) Infinite loop generated, because body_length is not Zero!!  ####

Step 3

194       # Build up the body length field bytes
195       begin
196         digit = (body_length % 128)        #### (3) digit changes to Rational [Rational % Fixnum => Rational] ####
197         body_length = (body_length / 128)
198         # ....
199         digit |= 0x80 if (body_length > 0)
200         header.push(digit)
201       end while (body_length > 0)

Step 4

195       begin
196         digit = (body_length % 128)
197         body_length = (body_length / 128)
198         # ....
199         digit |= 0x80 if (body_length > 0) #### (4) NoMethodError occured. Rational#| method is not defined... ####
200         header.push(digit)
201       end while (body_length > 0)
njh commented 8 years ago

Thanks for investigating yoggy.

So the key difference is that div will always return an integer - which is what we want because the remainder is being handled separately. We only what the whole number of divisions.

 "Uses / to perform division, then converts the result to an integer."

It doesn't specifically say if the conversion uses floor(), ceil() or round() but I think it is fairly safe to assume floor.

Also still unsure how to write a test for this.