Skip to content

Instantly share code, notes, and snippets.

@apeiros
Created May 1, 2011 20:07
Show Gist options
  • Save apeiros/950814 to your computer and use it in GitHub Desktop.
Save apeiros/950814 to your computer and use it in GitHub Desktop.
Float approximations in ruby
class Float
ExponentBits = 11 # 11 bits for exponent
SignificandBits = 52 # 52 bits for significand
ApproximativeEqualityDefault = 37 # precise to about 5 decimal places, which should be good for anything non-scientific
ApproximativeMaxExponent = 1<<ExponentBits
ApproximativeMaxSignificand = 1<<SignificandBits
ApproximativeMask1 = (1<<11)-1
ApproximativeMask2 = (1<<20)-1
ApproximativeMask3 = (1<<32)-1
def components
a,b = *[self].pack("G").unpack("NN") # sadly there's no unpack unsigned 64bit int in network order
[a[31], (a >> 20) & ApproximativeMask1, ((a & ApproximativeMask2) << 32) | b] # [bit 0, bits 1-11, bits 12-63]
end
def approximative_equality_key(e=ApproximativeEqualityDefault)
raise ArgumentError, "e must be a positive integer between 1 and 51" unless e.is_a?(Integer) && e.between?(1,51)
return self if nan? || infinite?
sign, exponent, significand = *components
factor = 1<<e
significand2 = significand.fdiv(factor).round*factor
if significand2 >= ApproximativeMaxSignificand then
significand2 = 0
exponent += 1
if exponent >= ApproximativeMaxExponent then
return Float::MAX # ugly, happy about nice refactors, bring it on :)
end
end
integers = [(sign<<31) | (exponent<<20) | ((significand2 >> 32) & ApproximativeMask2), (significand2 & ApproximativeMask3)]
binary = integers.pack("NN")
binary.unpack("G").first
end
def close?(other, e=ApproximativeEqualityDefault)
approximative_equality_key(e) == other.to_f.approximative_equality_key(e)
end
end
## Example:
if __FILE__ == $0 then
p [1.0, 1.0+Float::EPSILON, 2.0-Float::EPSILON, 2.0, 4.0].map { |v| v.approximative_equality_key }.uniq
# => [1.0, 2.0, 4.0]
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment