Skip to content

Commit e6aefcd

Browse files
authored
Fix unexpected rounding signs on OPT26- (#13365)
1 parent 73ef1c5 commit e6aefcd

File tree

3 files changed

+95
-76
lines changed

3 files changed

+95
-76
lines changed

.formatter.exs

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
assert_same: 2,
1414

1515
# Errors tests
16-
assert_eval_raise: 3
16+
assert_eval_raise: 3,
17+
18+
# Float tests
19+
float_assert: 1
1720
],
1821
normalize_bitstring_modifiers: false
1922
]

lib/elixir/lib/float.ex

+8-3
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ defmodule Float do
374374
case rounding do
375375
:ceil when sign === 0 -> 1 / power_of_10(precision)
376376
:floor when sign === 1 -> -1 / power_of_10(precision)
377-
:ceil when sign === 1 -> -0.0
378-
:half_up when sign === 1 -> -0.0
377+
:ceil when sign === 1 -> minus_zero()
378+
:half_up when sign === 1 -> minus_zero()
379379
_ -> 0.0
380380
end
381381

@@ -406,7 +406,7 @@ defmodule Float do
406406

407407
cond do
408408
num == 0 and sign == 1 ->
409-
-0.0
409+
minus_zero()
410410

411411
num == 0 ->
412412
0.0
@@ -422,6 +422,11 @@ defmodule Float do
422422
end
423423
end
424424

425+
# TODO remove once we require Erlang/OTP 27+
426+
# This function tricks the compiler to avoid this bug in previous versions:
427+
# https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/float.ex#L408-L412
428+
defp minus_zero, do: -0.0
429+
425430
defp decompose(significant, initial) do
426431
decompose(significant, 1, 0, initial)
427432
end

lib/elixir/test/elixir/float_test.exs

+83-72
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ defmodule FloatTest do
55

66
doctest Float
77

8+
# TODO remove and replace by assert once we require Erlang/OTP 27+
9+
# We can't easily distinguish between -0.0 and +0.0 on previous version
10+
defmacrop float_assert({:===, _, [left, right]}) do
11+
quote do
12+
# note: these are pure functions so no need to use bind_quoted
13+
# we favor a useful error message instead
14+
assert unquote(left) === unquote(right)
15+
assert to_string(unquote(left)) === to_string(unquote(right))
16+
end
17+
end
18+
819
test "parse/1" do
920
assert Float.parse("12") === {12.0, ""}
1021
assert Float.parse("-12") === {-12.0, ""}
@@ -45,153 +56,153 @@ defmodule FloatTest do
4556
end
4657

4758
test "floor/1" do
48-
assert Float.floor(12.524235) === 12.0
49-
assert Float.floor(-12.5) === -13.0
50-
assert Float.floor(-12.524235) === -13.0
51-
assert Float.floor(7.5e3) === 7500.0
52-
assert Float.floor(7.5432e3) === 7543.0
53-
assert Float.floor(7.5e-3) === 0.0
54-
assert Float.floor(-12.32453e4) === -123_246.0
55-
assert Float.floor(-12.32453e-10) === -1.0
56-
assert Float.floor(0.32453e-10) === 0.0
57-
assert Float.floor(-0.32453e-10) === -1.0
58-
assert Float.floor(1.32453e-10) === 0.0
59+
float_assert Float.floor(12.524235) === 12.0
60+
float_assert Float.floor(-12.5) === -13.0
61+
float_assert Float.floor(-12.524235) === -13.0
62+
float_assert Float.floor(7.5e3) === 7500.0
63+
float_assert Float.floor(7.5432e3) === 7543.0
64+
float_assert Float.floor(7.5e-3) === 0.0
65+
float_assert Float.floor(-12.32453e4) === -123_246.0
66+
float_assert Float.floor(-12.32453e-10) === -1.0
67+
float_assert Float.floor(0.32453e-10) === 0.0
68+
float_assert Float.floor(-0.32453e-10) === -1.0
69+
float_assert Float.floor(1.32453e-10) === 0.0
5970
end
6071

6172
describe "floor/2" do
6273
test "with 0.0" do
6374
for precision <- 0..15 do
64-
assert Float.floor(0.0, precision) === 0.0
65-
assert Float.floor(-0.0, precision) === -0.0
75+
float_assert Float.floor(0.0, precision) === 0.0
76+
float_assert Float.floor(-0.0, precision) === -0.0
6677
end
6778
end
6879

6980
test "floor/2 with precision" do
70-
assert Float.floor(12.524235, 0) === 12.0
71-
assert Float.floor(-12.524235, 0) === -13.0
81+
float_assert Float.floor(12.524235, 0) === 12.0
82+
float_assert Float.floor(-12.524235, 0) === -13.0
7283

73-
assert Float.floor(12.52, 2) === 12.51
74-
assert Float.floor(-12.52, 2) === -12.52
84+
float_assert Float.floor(12.52, 2) === 12.51
85+
float_assert Float.floor(-12.52, 2) === -12.52
7586

76-
assert Float.floor(12.524235, 2) === 12.52
77-
assert Float.floor(-12.524235, 3) === -12.525
87+
float_assert Float.floor(12.524235, 2) === 12.52
88+
float_assert Float.floor(-12.524235, 3) === -12.525
7889

79-
assert Float.floor(12.32453e-20, 2) === 0.0
80-
assert Float.floor(-12.32453e-20, 2) === -0.01
90+
float_assert Float.floor(12.32453e-20, 2) === 0.0
91+
float_assert Float.floor(-12.32453e-20, 2) === -0.01
8192

8293
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
8394
Float.floor(1.1, 16)
8495
end
8596
end
8697

8798
test "with subnormal floats" do
88-
assert Float.floor(-5.0e-324, 0) === -1.0
89-
assert Float.floor(-5.0e-324, 1) === -0.1
90-
assert Float.floor(-5.0e-324, 2) === -0.01
91-
assert Float.floor(-5.0e-324, 15) === -0.000000000000001
99+
float_assert Float.floor(-5.0e-324, 0) === -1.0
100+
float_assert Float.floor(-5.0e-324, 1) === -0.1
101+
float_assert Float.floor(-5.0e-324, 2) === -0.01
102+
float_assert Float.floor(-5.0e-324, 15) === -0.000000000000001
92103

93104
for precision <- 0..15 do
94-
assert Float.floor(5.0e-324, precision) === 0.0
105+
float_assert Float.floor(5.0e-324, precision) === 0.0
95106
end
96107
end
97108
end
98109

99110
test "ceil/1" do
100-
assert Float.ceil(12.524235) === 13.0
101-
assert Float.ceil(-12.5) === -12.0
102-
assert Float.ceil(-12.524235) === -12.0
103-
assert Float.ceil(7.5e3) === 7500.0
104-
assert Float.ceil(7.5432e3) === 7544.0
105-
assert Float.ceil(7.5e-3) === 1.0
106-
assert Float.ceil(-12.32453e4) === -123_245.0
107-
assert Float.ceil(-12.32453e-10) === -0.0
108-
assert Float.ceil(0.32453e-10) === 1.0
109-
assert Float.ceil(-0.32453e-10) === -0.0
110-
assert Float.ceil(1.32453e-10) === 1.0
111-
assert Float.ceil(0.0) === 0.0
111+
float_assert Float.ceil(12.524235) === 13.0
112+
float_assert Float.ceil(-12.5) === -12.0
113+
float_assert Float.ceil(-12.524235) === -12.0
114+
float_assert Float.ceil(7.5e3) === 7500.0
115+
float_assert Float.ceil(7.5432e3) === 7544.0
116+
float_assert Float.ceil(7.5e-3) === 1.0
117+
float_assert Float.ceil(-12.32453e4) === -123_245.0
118+
float_assert Float.ceil(-12.32453e-10) === -0.0
119+
float_assert Float.ceil(0.32453e-10) === 1.0
120+
float_assert Float.ceil(-0.32453e-10) === -0.0
121+
float_assert Float.ceil(1.32453e-10) === 1.0
122+
float_assert Float.ceil(0.0) === 0.0
112123
end
113124

114125
describe "ceil/2" do
115126
test "with 0.0" do
116127
for precision <- 0..15 do
117-
assert Float.ceil(0.0, precision) === 0.0
118-
assert Float.ceil(-0.0, precision) === -0.0
128+
float_assert Float.ceil(0.0, precision) === 0.0
129+
float_assert Float.ceil(-0.0, precision) === -0.0
119130
end
120131
end
121132

122133
test "with regular floats" do
123-
assert Float.ceil(12.524235, 0) === 13.0
124-
assert Float.ceil(-12.524235, 0) === -12.0
134+
float_assert Float.ceil(12.524235, 0) === 13.0
135+
float_assert Float.ceil(-12.524235, 0) === -12.0
125136

126-
assert Float.ceil(12.52, 2) === 12.52
127-
assert Float.ceil(-12.52, 2) === -12.51
137+
float_assert Float.ceil(12.52, 2) === 12.52
138+
float_assert Float.ceil(-12.52, 2) === -12.51
128139

129-
assert Float.ceil(12.524235, 2) === 12.53
130-
assert Float.ceil(-12.524235, 3) === -12.524
140+
float_assert Float.ceil(12.524235, 2) === 12.53
141+
float_assert Float.ceil(-12.524235, 3) === -12.524
131142

132-
assert Float.ceil(12.32453e-20, 2) === 0.01
133-
assert Float.ceil(-12.32453e-20, 2) === -0.0
143+
float_assert Float.ceil(12.32453e-20, 2) === 0.01
144+
float_assert Float.ceil(-12.32453e-20, 2) === -0.0
134145

135-
assert Float.ceil(0.0, 2) === 0.0
146+
float_assert Float.ceil(0.0, 2) === 0.0
136147

137148
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
138149
Float.ceil(1.1, 16)
139150
end
140151
end
141152

142153
test "with small floats rounded up to -0.0" do
143-
assert Float.ceil(-0.1, 0) === -0.0
144-
assert Float.ceil(-0.01, 1) === -0.0
154+
float_assert Float.ceil(-0.1, 0) === -0.0
155+
float_assert Float.ceil(-0.01, 1) === -0.0
145156
end
146157

147158
test "with subnormal floats" do
148-
assert Float.ceil(5.0e-324, 0) === 1.0
149-
assert Float.ceil(5.0e-324, 1) === 0.1
150-
assert Float.ceil(5.0e-324, 2) === 0.01
151-
assert Float.ceil(5.0e-324, 15) === 0.000000000000001
159+
float_assert Float.ceil(5.0e-324, 0) === 1.0
160+
float_assert Float.ceil(5.0e-324, 1) === 0.1
161+
float_assert Float.ceil(5.0e-324, 2) === 0.01
162+
float_assert Float.ceil(5.0e-324, 15) === 0.000000000000001
152163

153164
for precision <- 0..15 do
154-
assert Float.ceil(-5.0e-324, precision) === -0.0
165+
float_assert Float.ceil(-5.0e-324, precision) === -0.0
155166
end
156167
end
157168
end
158169

159170
describe "round/2" do
160171
test "with 0.0" do
161172
for precision <- 0..15 do
162-
assert Float.round(0.0, precision) === 0.0
163-
assert Float.round(-0.0, precision) === -0.0
173+
float_assert Float.round(0.0, precision) === 0.0
174+
float_assert Float.round(-0.0, precision) === -0.0
164175
end
165176
end
166177

167178
test "with regular floats" do
168-
assert Float.round(5.5675, 3) === 5.567
169-
assert Float.round(-5.5674, 3) === -5.567
170-
assert Float.round(5.5, 3) === 5.5
171-
assert Float.round(5.5e-10, 10) === 5.0e-10
172-
assert Float.round(5.5e-10, 8) === 0.0
173-
assert Float.round(5.0, 0) === 5.0
179+
float_assert Float.round(5.5675, 3) === 5.567
180+
float_assert Float.round(-5.5674, 3) === -5.567
181+
float_assert Float.round(5.5, 3) === 5.5
182+
float_assert Float.round(5.5e-10, 10) === 5.0e-10
183+
float_assert Float.round(5.5e-10, 8) === 0.0
184+
float_assert Float.round(5.0, 0) === 5.0
174185

175186
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
176187
Float.round(1.1, 16)
177188
end
178189
end
179190

180191
test "with small floats rounded to +0.0 / -0.0" do
181-
assert Float.round(0.01, 0) === 0.0
182-
assert Float.round(0.01, 1) === 0.0
192+
float_assert Float.round(0.01, 0) === 0.0
193+
float_assert Float.round(0.01, 1) === 0.0
183194

184-
assert Float.round(-0.01, 0) === -0.0
185-
assert Float.round(-0.01, 1) === -0.0
195+
float_assert Float.round(-0.01, 0) === -0.0
196+
float_assert Float.round(-0.01, 1) === -0.0
186197

187-
assert Float.round(-0.49999, 0) === -0.0
188-
assert Float.round(-0.049999, 1) === -0.0
198+
float_assert Float.round(-0.49999, 0) === -0.0
199+
float_assert Float.round(-0.049999, 1) === -0.0
189200
end
190201

191202
test "with subnormal floats" do
192203
for precision <- 0..15 do
193-
assert Float.round(5.0e-324, precision) === 0.0
194-
assert Float.round(-5.0e-324, precision) === -0.0
204+
float_assert Float.round(5.0e-324, precision) === 0.0
205+
float_assert Float.round(-5.0e-324, precision) === -0.0
195206
end
196207
end
197208
end

0 commit comments

Comments
 (0)