Skip to content
This repository has been archived by the owner on Apr 17, 2018. It is now read-only.

Numeric validator precision/scale validation fails for certain floats on MRI 1.9.3 #51

Open
ashervb opened this issue Jan 9, 2012 · 2 comments

Comments

@ashervb
Copy link

ashervb commented Jan 9, 2012

In validators/numeric_validator.rb on line 38

      def value_as_string(value)
        case value
          # Avoid Scientific Notation in Float to_s
          when Float      then value.to_d.to_s('F')
          when BigDecimal then value.to_s('F')
          else value.to_s
        end
      end

Given: f=-75.6942185

We call, f.to_d.to_s('F')

On all rubies except 1.9.3 we get:

"-75.6942185"

On MRI 1.9.3 we get:

"-75.69421850000001"

When then causes the validator to report an invalid precision/scale

@krzysztofjablonski
Copy link

The same problem with number: 842.31

1.9.3p0 :001 > 842.31.to_d.to_s
=> "842.3099999999999" 

This means that we can have random validation errors ("Value is not a number") during save model.

In ruby 1.9.3 method to_d in Foat class changed:

class Float < Numeric
  def to_d(precision=nil)
    BigDecimal(self, precision || Float::DIG+1)
  end
end

In ruby 1.9.2 it was:

class Float < Numeric
  def to_d
    BigDecimal(self.to_s)
  end
end

This is why it works in 1.9.2 and doesn't work in 1.9.3.

1.9.3p0 :008 > BigDecimal(842.31, Float::DIG+1).to_s
 => "842.3099999999999" 
1.9.3p0 :009 > BigDecimal(842.31.to_s).to_s
 => "842.31" 

To fix it in your app you can override to_d method like this:

class Float < Numeric
  def to_d(precision=nil)
   if precision
     BigDecimal(self, precision)
   else
     BigDecimal(self.to_s)
   end
  end
end

but this monkeypatch is not the best idea and it should be fixed somehow in DM.

@krzysztofjablonski
Copy link

This is safer fix (for DM-core ~> 1.1.0):

# Ruby 1.9.3 hack

module DataMapper
  class Property
    class Decimal193 < Decimal
      def typecast_to_primitive(value)
        if value.kind_of?(::Integer)
          value.to_s.to_d
        elsif value.kind_of?(::Float)
          BigDecimal(value.to_s)
        else
          typecast_to_numeric(value, :to_d)
        end
      end
    end
  end
end

and then use Decimal193 instead of Decimal in your models.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants