Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make float output identical to JSON gem #19

Merged
merged 9 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 3 additions & 1 deletion test/test_jsonchecker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class TestJsonchecker < Minitest::Test
assert_match re, ex.message
else
assert RapidJSON.valid_json?(original_json)
assert RapidJSON.parse(original_json)
parsed = RapidJSON.parse(original_json)
assert parsed
assert RapidJSON.json_ready?(parsed)
end
end
end
Expand Down
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