Created
May 1, 2011 20:07
-
-
Save apeiros/950814 to your computer and use it in GitHub Desktop.
Float approximations in ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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