Skip to content

Commit eb0dff2

Browse files
committed
fixes #26
1 parent 44c7ecf commit eb0dff2

File tree

3 files changed

+96
-15
lines changed

3 files changed

+96
-15
lines changed

lib/bson/decimal128.ex

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
defmodule BSON.Decimal128 do
2+
3+
@moduledoc """
4+
see https://en.wikipedia.org/wiki/Decimal128_floating-point_format
5+
"""
6+
27
use Bitwise
38

49
@signed_bit_mask 1 <<< 63
@@ -9,13 +14,20 @@ defmodule BSON.Decimal128 do
914
@exponent_bias 6176
1015
@max_exponent 6111
1116
@min_exponent -6176
17+
@s_nan_mask 0x1 <<< 57
18+
@significand_mask ((0x1 <<< 49)-1)
19+
@low_mask 0xffffffffffffffff
1220

1321
def decode(<<_::little-64, high::little-64>> = bits) do
1422
is_negative = (high &&& @signed_bit_mask) == (@signed_bit_mask)
1523
combination = (high >>> 58 &&& @combination_mask)
1624
two_highest_bits_set = combination >>> 3 == 3
1725
is_infinity = two_highest_bits_set && combination == @combintation_infinity
18-
is_nan = two_highest_bits_set && combination == @combintation_nan
26+
is_nan = case {(two_highest_bits_set && combination) == @combintation_nan, (high &&& @s_nan_mask) == @s_nan_mask} do
27+
{true, true} -> :sNan
28+
{true, false} -> :qNan
29+
_ -> false
30+
end
1931

2032
exponent = exponent(high, two_highest_bits_set)
2133

@@ -29,10 +41,35 @@ defmodule BSON.Decimal128 do
2941
)
3042
end
3143

44+
@doc """
45+
s 11110 xx...x ±infinity
46+
s 11111 0x...x a quiet NaN
47+
s 11111 1x...x a signalling NaN
48+
"""
49+
def encode(%Decimal{sign: -1, coef: :inf}) do
50+
low = 0
51+
high = 0x3e <<< 58
52+
<<low::little-64, high::little-64>>
53+
end
54+
def encode(%Decimal{coef: :inf}) do
55+
low = 0
56+
high = 0x1e <<< 58
57+
<<low::little-64, high::little-64>>
58+
end
59+
def encode(%Decimal{coef: :qNaN}) do
60+
low = 0
61+
high = 0x1f <<< 58
62+
<<low::little-64, high::little-64>>
63+
end
64+
def encode(%Decimal{coef: :sNaN}) do
65+
low = 0
66+
high = 0x3f <<< 57
67+
<<low::little-64, high::little-64>>
68+
end
3269
def encode(%Decimal{sign: sign, coef: significand, exp: exponent}) when exponent >= @min_exponent and exponent <= @max_exponent do
3370
biasedExponent = exponent + @exponent_bias
34-
low = significand &&& 0xffffffff
35-
high = (significand >>> 64) &&& 0xffffffff
71+
low = significand &&& @low_mask
72+
high = (significand >>> 64) &&& @significand_mask ## mask max significand
3673
high = bor(high, biasedExponent <<< 49)
3774
high = case sign do
3875
1 -> high
@@ -41,7 +78,6 @@ defmodule BSON.Decimal128 do
4178

4279
<<low::little-64, high::little-64>>
4380
end
44-
4581
def encode(%Decimal{exp: exponent}) do
4682
message = "Exponent is out of range for Decimal128 encoding, #{exponent}"
4783
raise ArgumentError, message
@@ -51,7 +87,6 @@ defmodule BSON.Decimal128 do
5187
biased_exponent = (high >>> 47) &&& @exponent_mask
5288
biased_exponent - @exponent_bias
5389
end
54-
5590
defp exponent(high, _two_highest_bits_not_set) do
5691
biased_exponent = (high >>> 49) &&& @exponent_mask
5792
biased_exponent - @exponent_bias
@@ -60,23 +95,21 @@ defmodule BSON.Decimal128 do
6095
defp value(%{is_negative: true, is_infinity: true}, _, _) do
6196
%Decimal{sign: -1, coef: :inf}
6297
end
63-
6498
defp value(%{is_negative: false, is_infinity: true}, _, _) do
6599
%Decimal{coef: :inf}
66100
end
67-
68-
defp value(%{is_nan: true}, _, _) do
101+
defp value(%{is_nan: :qNan}, _, _) do
69102
%Decimal{coef: :qNaN}
70103
end
71-
104+
defp value(%{is_nan: :sNan}, _, _) do
105+
%Decimal{coef: :sNaN}
106+
end
72107
defp value(%{two_highest_bits_set: true}, _, _) do
73108
%Decimal{sign: 0, coef: 0, exp: 0}
74109
end
75-
76110
defp value(%{is_negative: true}, coef, exponent) do
77111
%Decimal{sign: -1, coef: coef, exp: exponent}
78112
end
79-
80113
defp value(_, coef, exponent) do
81114
%Decimal{coef: coef, exp: exponent}
82115
end

test/bson/decimal128_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,33 @@ defmodule BSON.Decimal128Test do
4040
assert_decimal(@binaries_neg_tiny, %Decimal{sign: -1, coef: 1, exp: -6176})
4141
end
4242

43+
@tag :mongo_3_4
44+
test "BSON.Decimal128.encode/1" do
45+
assert_decimal(%Decimal{coef: :qNaN})
46+
assert_decimal(%Decimal{coef: :sNaN})
47+
assert_decimal(%Decimal{sign: -1, coef: :inf})
48+
assert_decimal(%Decimal{coef: :inf})
49+
assert_decimal(%Decimal{coef: 0, exp: -611})
50+
assert_decimal(%Decimal{sign: -1, coef: 0, exp: -1})
51+
assert_decimal(%Decimal{coef: 1, exp: 3})
52+
assert_decimal(%Decimal{coef: 1234, exp: -6})
53+
assert_decimal(%Decimal{coef: 123400000, exp: -11})
54+
assert_decimal(%Decimal{coef: 1234567890123456789012345678901234, exp: -34})
55+
assert_decimal(%Decimal{coef: 1234567890123456789012345678901234, exp: 0})
56+
assert_decimal(%Decimal{coef: 9999999999999999999999999999999999, exp: -6176})
57+
assert_decimal(%Decimal{coef: 1, exp: -6176})
58+
assert_decimal(%Decimal{sign: -1, coef: 1, exp: -6176})
59+
end
60+
61+
defp assert_decimal(expected_decimal) do
62+
63+
value = expected_decimal
64+
|> BSON.Decimal128.encode()
65+
|> BSON.Decimal128.decode()
66+
67+
assert value == expected_decimal
68+
end
69+
4370
defp assert_decimal(binaries, expected_decimal) do
4471
assert BSON.Decimal128.decode(binaries) == expected_decimal
4572
end

test/mongo_test.exs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,31 @@ defmodule Mongo.Test do
556556
@tag :mongo_3_4
557557
test "correctly query NumberDecimal", c do
558558
coll = "number_decimal_test"
559-
Mongo.insert_one(c.pid, coll, %{number: Decimal.from_float(123.456), index: 1})
560-
Mongo.insert_one(c.pid, coll, %{number: Decimal.from_float(-123.456), index: 2})
561-
assert %{"number" => %Decimal{coef: 123456, exp: -3}} = Mongo.find(c.pid, coll, %{index: 1}, limit: 1) |> Enum.to_list |> List.first()
562-
assert %{"number" => %Decimal{sign: -1, coef: 123456, exp: -3}} = Mongo.find(c.pid, coll, %{index: 2}, limit: 1) |> Enum.to_list |> List.first()
559+
560+
Mongo.delete_many(c.pid, coll, %{})
561+
562+
values = [
563+
%Decimal{coef: :qNaN},
564+
%Decimal{coef: :sNaN},
565+
%Decimal{sign: -1, coef: :inf},
566+
%Decimal{coef: :inf},
567+
%Decimal{coef: 0, exp: -611},
568+
%Decimal{sign: -1, coef: 0, exp: -1},
569+
%Decimal{coef: 1, exp: 3},
570+
%Decimal{coef: 1234, exp: -6},
571+
%Decimal{coef: 123400000, exp: -11},
572+
%Decimal{coef: 1234567890123456789012345678901234, exp: -34},
573+
%Decimal{coef: 1234567890123456789012345678901234, exp: 0},
574+
%Decimal{coef: 9999999999999999999999999999999999, exp: -6176},
575+
%Decimal{coef: 1, exp: -6176},
576+
%Decimal{sign: -1, coef: 1, exp: -6176}] |> Enum.with_index()
577+
578+
Enum.each(values, fn {dec, i} -> Mongo.insert_one(c.pid, coll, %{number: dec, index: i}) end)
579+
580+
Enum.each(values, fn {dec, i} ->
581+
assert %{"number" => ^dec} = Mongo.find(c.pid, coll, %{index: i}, limit: 1) |> Enum.to_list |> List.first()
582+
end)
583+
563584
end
564585

565586
test "access multiple databases", c do

0 commit comments

Comments
 (0)