Skip to content

Commit

Permalink
Merge pull request #19 from jhawthorn/float_compatibility
Browse files Browse the repository at this point in the history
Make float output identical to JSON gem
  • Loading branch information
jhawthorn authored Sep 5, 2024
2 parents 5a7ac2a + 1956c24 commit f0bb65d
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 6 deletions.
7 changes: 6 additions & 1 deletion ext/rapidjson/encoder.hh
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,12 @@ class RubyObjectEncoder {
}
}
} else {
writer.Double(f);
// TODO: We should avoid relying on to_s and do this conversion
// ourselves. However it's difficult to get the exact same rounding
// and truncation that Ruby uses.
VALUE str = rb_funcall(v, rb_intern("to_s"), 0);
Check_Type(str, T_STRING);
encode_raw_json_str(str);
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/data/roundtrip/roundtrip24.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[5e-324]
[5.0e-324]
2 changes: 1 addition & 1 deletion test/data/roundtrip/roundtrip27.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[1.7976931348623157e308]
[1.7976931348623157e+308]
54 changes: 54 additions & 0 deletions test/test_allocations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require "test_helper"

class TestAllocations < Minitest::Test
def test_integer
assert_encode_allocations 1, [1, 2, 3, 4, 5]
end

def test_boolean
assert_encode_allocations 1, [true, false, true, false, true, false]
end

def test_float
# FIXME: ideally would not allocate
assert_encode_allocations 6, [1.0, 2.0, 3.0, 4.0, 5.0]
end

def test_symbol
assert_encode_allocations 1, %i[foo bar baz quux]
end

def test_string
assert_encode_allocations 1, %w[foo bar baz quux]
end

def test_array
assert_encode_allocations 1, [[], [], [], [], []]
end

def test_hash
assert_encode_allocations 1, {foo: 1, bar: 2, baz: 3, quux: 4}
end

def assert_encode_allocations(expected, data)
allocations = measure_allocations { RapidJSON.encode(data) }
assert_equal expected, allocations
end

def measure_allocations
i = 0
while i < 2
before = allocations
yield
after = allocations
i += 1
end
after - before
end

def allocations
GC.stat(:total_allocated_objects)
end
end
55 changes: 53 additions & 2 deletions test/test_encoder_compatibility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,47 @@ def test_encode_float
assert_compat 0.0
assert_compat(-0.0)
assert_compat 155.0

# Found via random test, this is the exact representation
assert_compat 103876218730131.625
assert_compat(-169986783765216.875)

0.upto(1023) do |e|
assert_compat(2.0 ** e)
assert_compat(2.0 ** -e)
end
end

def test_encode_randomized_floats
1000.times do
f = [rand(2**64)].pack("Q").unpack1("D")
next if f.nan? || f.infinite?
assert_compat(f)
end
end

def test_float_scientific_threshold
assert_implementations_equal do |json|
(1.0..).bsearch{ |x| json.dump(x).include?("e") }
end

assert_implementations_equal do |json|
(1.0..).bsearch{ |x| json.dump(-x).include?("e") }
end

assert_implementations_equal do |json|
(1.0..).bsearch{ |x| json.dump(1.0 / x).include?("e") }
end

assert_implementations_equal do |json|
(1.0..).bsearch{ |x| json.dump(-1.0 / x).include?("e") }
end
end

def test_encode_limits
RbConfig::LIMITS.each_value do |v|
assert_compat(v)
end
end

def test_encode_hash
Expand Down Expand Up @@ -175,10 +216,20 @@ def assert_compat(object)
end

def assert_dump_equal(object, *args)
assert_equal ::JSON.dump(object, *args), RapidJSON::JSONGem.dump(object, *args)
assert_implementations_equal do |json|
json.dump(object, *args)
end
end

def assert_generate_equal(object, *args)
assert_equal ::JSON.generate(object, *args), RapidJSON::JSONGem.generate(object, *args)
assert_implementations_equal do |json|
json.generate(object, *args)
end
end

def assert_implementations_equal(&block)
expected = yield(::JSON)
actual = yield(RapidJSON::JSONGem)
assert_equal expected, actual
end
end
2 changes: 1 addition & 1 deletion test/test_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,6 @@ def test_parse_NaN_and_Infinity_allowed

assert_predicate coder.load("NaN"), :nan?
assert_equal Float::INFINITY, coder.load("Inf")
assert_equal -Float::INFINITY, coder.load("-Inf")
assert_equal(-Float::INFINITY, coder.load("-Inf"))
end
end

0 comments on commit f0bb65d

Please sign in to comment.