Skip to content

Commit 3d04cb0

Browse files
josevalimJosé Valim
authored andcommitted
Fix rounding for subnormal floats (#8687)
Closes #8685 Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent 8fa9d72 commit 3d04cb0

File tree

2 files changed

+98
-38
lines changed

2 files changed

+98
-38
lines changed

lib/elixir/lib/float.ex

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,16 +268,14 @@ defmodule Float do
268268
raise ArgumentError, invalid_precision_message(precision)
269269
end
270270

271+
defp round(0.0, _precision, _rounding), do: 0.0
272+
271273
defp round(float, precision, rounding) do
272274
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
273275
{num, count, _} = decompose(significant, 1)
274276
count = count - exp + 1023
275277

276278
cond do
277-
# There is no decimal precision on subnormal floats
278-
count <= 0 or exp == 0 ->
279-
float
280-
281279
# Precision beyond 15 digits
282280
count >= 104 ->
283281
case rounding do
@@ -444,11 +442,11 @@ defmodule Float do
444442
{acc, last_count, last_power}
445443
end
446444

445+
@compile {:inline, sign: 2, shift_left: 2}
447446
defp sign(0, num), do: num
448447
defp sign(1, num), do: -num
449448

450-
defp shift_left(num, 0), do: num
451-
defp shift_left(num, times), do: shift_left(num <<< 1, times - 1)
449+
defp shift_left(num, times), do: num <<< times
452450

453451
defp shift_right(num, 0), do: {num, 0}
454452
defp shift_right(1, times), do: {1, times}

lib/elixir/test/elixir/float_test.exs

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,41 @@ defmodule FloatTest do
5555
assert Float.floor(1.32453e-10) === 0.0
5656
end
5757

58-
test "floor/2 with precision" do
59-
assert Float.floor(12.524235, 0) === 12.0
60-
assert Float.floor(-12.524235, 0) === -13.0
58+
describe "floor/2" do
59+
test "with 0.0" do
60+
for precision <- 0..15 do
61+
assert Float.floor(0.0, precision) === 0.0
62+
assert Float.floor(-0.0, precision) === -0.0
63+
end
64+
end
65+
66+
test "floor/2 with precision" do
67+
assert Float.floor(12.524235, 0) === 12.0
68+
assert Float.floor(-12.524235, 0) === -13.0
6169

62-
assert Float.floor(12.52, 2) === 12.51
63-
assert Float.floor(-12.52, 2) === -12.52
70+
assert Float.floor(12.52, 2) === 12.51
71+
assert Float.floor(-12.52, 2) === -12.52
6472

65-
assert Float.floor(12.524235, 2) === 12.52
66-
assert Float.floor(-12.524235, 3) === -12.525
73+
assert Float.floor(12.524235, 2) === 12.52
74+
assert Float.floor(-12.524235, 3) === -12.525
6775

68-
assert Float.floor(12.32453e-20, 2) === 0.0
69-
assert Float.floor(-12.32453e-20, 2) === -0.01
76+
assert Float.floor(12.32453e-20, 2) === 0.0
77+
assert Float.floor(-12.32453e-20, 2) === -0.01
78+
79+
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
80+
Float.floor(1.1, 16)
81+
end
82+
end
7083

71-
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
72-
Float.floor(1.1, 16)
84+
test "with subnormal floats" do
85+
assert Float.floor(-5.0e-324, 0) === -1.0
86+
assert Float.floor(-5.0e-324, 1) === -0.1
87+
assert Float.floor(-5.0e-324, 2) === -0.01
88+
assert Float.floor(-5.0e-324, 15) === -0.000000000000001
89+
90+
for precision <- 0..15 do
91+
assert Float.floor(5.0e-324, precision) === 0.0
92+
end
7393
end
7494
end
7595

@@ -88,36 +108,72 @@ defmodule FloatTest do
88108
assert Float.ceil(0.0) === 0.0
89109
end
90110

91-
test "ceil/2 with precision" do
92-
assert Float.ceil(12.524235, 0) === 13.0
93-
assert Float.ceil(-12.524235, 0) === -12.0
111+
describe "ceil/2" do
112+
test "with 0.0" do
113+
for precision <- 0..15 do
114+
assert Float.ceil(0.0, precision) === 0.0
115+
assert Float.ceil(-0.0, precision) === -0.0
116+
end
117+
end
94118

95-
assert Float.ceil(12.52, 2) === 12.52
96-
assert Float.ceil(-12.52, 2) === -12.51
119+
test "with regular floats" do
120+
assert Float.ceil(12.524235, 0) === 13.0
121+
assert Float.ceil(-12.524235, 0) === -12.0
97122

98-
assert Float.ceil(12.524235, 2) === 12.53
99-
assert Float.ceil(-12.524235, 3) === -12.524
123+
assert Float.ceil(12.52, 2) === 12.52
124+
assert Float.ceil(-12.52, 2) === -12.51
100125

101-
assert Float.ceil(12.32453e-20, 2) === 0.01
102-
assert Float.ceil(-12.32453e-20, 2) === 0.0
126+
assert Float.ceil(12.524235, 2) === 12.53
127+
assert Float.ceil(-12.524235, 3) === -12.524
103128

104-
assert Float.ceil(0.0, 2) === 0.0
129+
assert Float.ceil(12.32453e-20, 2) === 0.01
130+
assert Float.ceil(-12.32453e-20, 2) === 0.0
105131

106-
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
107-
Float.ceil(1.1, 16)
132+
assert Float.ceil(0.0, 2) === 0.0
133+
134+
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
135+
Float.ceil(1.1, 16)
136+
end
137+
end
138+
139+
test "with subnormal floats" do
140+
assert Float.ceil(5.0e-324, 0) === 1.0
141+
assert Float.ceil(5.0e-324, 1) === 0.1
142+
assert Float.ceil(5.0e-324, 2) === 0.01
143+
assert Float.ceil(5.0e-324, 15) === 0.000000000000001
144+
145+
for precision <- 0..15 do
146+
assert Float.ceil(-5.0e-324, precision) === -0.0
147+
end
108148
end
109149
end
110150

111-
test "round/2" do
112-
assert Float.round(5.5675, 3) === 5.567
113-
assert Float.round(-5.5674, 3) === -5.567
114-
assert Float.round(5.5, 3) === 5.5
115-
assert Float.round(5.5e-10, 10) === 5.0e-10
116-
assert Float.round(5.5e-10, 8) === 0.0
117-
assert Float.round(5.0, 0) === 5.0
151+
describe "round/2" do
152+
test "with 0.0" do
153+
for precision <- 0..15 do
154+
assert Float.round(0.0, precision) === 0.0
155+
assert Float.round(-0.0, precision) === -0.0
156+
end
157+
end
118158

119-
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
120-
Float.round(1.1, 16)
159+
test "with regular floats" do
160+
assert Float.round(5.5675, 3) === 5.567
161+
assert Float.round(-5.5674, 3) === -5.567
162+
assert Float.round(5.5, 3) === 5.5
163+
assert Float.round(5.5e-10, 10) === 5.0e-10
164+
assert Float.round(5.5e-10, 8) === 0.0
165+
assert Float.round(5.0, 0) === 5.0
166+
167+
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
168+
Float.round(1.1, 16)
169+
end
170+
end
171+
172+
test "with subnormal floats" do
173+
for precision <- 0..15 do
174+
assert Float.round(5.0e-324, precision) === 0.0
175+
assert Float.round(-5.0e-324, precision) === -0.0
176+
end
121177
end
122178
end
123179

@@ -126,6 +182,12 @@ defmodule FloatTest do
126182
assert Float.ratio(0.0) == {0, 1}
127183
end
128184

185+
test "with regular floats" do
186+
assert Float.ratio(3.14) == {7_070_651_414_971_679, 2_251_799_813_685_248}
187+
assert Float.ratio(-3.14) == {-7_070_651_414_971_679, 2_251_799_813_685_248}
188+
assert Float.ratio(1.5) == {3, 2}
189+
end
190+
129191
test "with subnormal floats" do
130192
assert Float.ratio(5.0e-324) ==
131193
{1,

0 commit comments

Comments
 (0)