Skip to content

Commit b17d3a6

Browse files
authored
Support 16bit floats in bitstrings (#10740)
On OTP 24: iex> <<x::float-16>> = <<60, 0>> iex> x 1.0 iex> <<x::float-16>> <<60, 0>> Before OTP 24 we'd get errors or wouldn't match: iex> <<1.0::float-16>> ** (ArgumentError) argument error while evaluating iex at line 1 <<x::float-16>> = <<60, 0>> ** (MatchError) no match of right hand side value: <<60, 0>> iex> (fn <<x::float-16>> -> x; _ -> :nomatch end).(<<60, 0>>) :nomatch
1 parent be4cb59 commit b17d3a6

File tree

3 files changed

+60
-12
lines changed

3 files changed

+60
-12
lines changed

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,9 @@ defmodule Kernel.SpecialForms do
233233
234234
Sizes for types are a bit more nuanced. The default size for integers is 8.
235235
236-
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
236+
For floats, it is 64. For floats, `size * unit` must result in 16, 32, or 64,
237237
corresponding to [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)
238-
binary32 and binary64, respectively.
238+
binary16, binary32, and binary64, respectively.
239239
240240
For binaries, the default is the size of the binary. Only the last binary in a
241241
match can use the default size. All others must have their size specified

lib/elixir/src/elixir_bitstring.erl

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,13 @@ build_spec(Meta, _Size, Unit, Type, _Endianness, Sign, Spec, E) when Type == bin
319319
build_spec(Meta, Size, Unit, Type, Endianness, Sign, Spec, E) when Type == integer; Type == float ->
320320
NumberSize = number_size(Size, Unit),
321321
if
322-
Type == float, is_integer(NumberSize), NumberSize /= 32, NumberSize /= 64 ->
323-
form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize});
322+
Type == float, is_integer(NumberSize) ->
323+
case valid_float_size(NumberSize) of
324+
true ->
325+
add_spec(Type, add_spec(Endianness, add_spec(Sign, Spec)));
326+
false ->
327+
form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize})
328+
end;
324329
Size == default, Unit /= default ->
325330
form_error(Meta, E, ?MODULE, bittype_unit);
326331
true ->
@@ -331,6 +336,12 @@ number_size(Size, default) when is_integer(Size) -> Size;
331336
number_size(Size, Unit) when is_integer(Size) -> Size * Unit;
332337
number_size(Size, _) -> Size.
333338

339+
%% TODO: Simplify when we require OTP 24
340+
valid_float_size(16) -> erlang:system_info(otp_release) >= "24";
341+
valid_float_size(32) -> true;
342+
valid_float_size(64) -> true;
343+
valid_float_size(_) -> false.
344+
334345
add_spec(default, Spec) -> Spec;
335346
add_spec(Key, Spec) -> [{Key, [], []} | Spec].
336347

@@ -372,7 +383,12 @@ format_error(bittype_signed) ->
372383
format_error(bittype_unit) ->
373384
"integer and float types require a size specifier if the unit specifier is given";
374385
format_error({bittype_float_size, Other}) ->
375-
io_lib:format("float requires size*unit to be 32 or 64 (default), got: ~p", [Other]);
386+
Message =
387+
case erlang:system_info(otp_release) >= "24" of
388+
true -> "16, 32, or 64";
389+
false -> "32 or 64"
390+
end,
391+
io_lib:format("float requires size*unit to be ~s (default), got: ~p", [Message, Other]);
376392
format_error({invalid_literal, Literal}) ->
377393
io_lib:format("invalid literal ~ts in <<>>", ['Elixir.Macro':to_string(Literal)]);
378394
format_error({undefined_bittype, Expr}) ->

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,17 +2387,49 @@ defmodule Kernel.ExpansionTest do
23872387
end
23882388
end
23892389

2390-
test "raises for invalid size * unit for floats" do
2391-
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128"
2390+
# TODO: Simplify when we require OTP 24
2391+
if System.otp_release() >= "24" do
2392+
test "16-bit floats" do
2393+
import Kernel, except: [-: 2]
23922394

2393-
assert_raise CompileError, message, fn ->
2394-
expand(quote(do: <<12.3::32*4>>))
2395+
assert expand(quote(do: <<12.3::float-16>>)) |> clean_meta([:alignment]) ==
2396+
quote(do: <<12.3::float()-size(16)>>)
23952397
end
23962398

2397-
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256"
2399+
test "raises for invalid size * unit for floats" do
2400+
message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 128"
23982401

2399-
assert_raise CompileError, message, fn ->
2400-
expand(quote(do: <<12.3::256>>))
2402+
assert_raise CompileError, message, fn ->
2403+
expand(quote(do: <<12.3::32*4>>))
2404+
end
2405+
2406+
message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 256"
2407+
2408+
assert_raise CompileError, message, fn ->
2409+
expand(quote(do: <<12.3::256>>))
2410+
end
2411+
end
2412+
else
2413+
test "16-bit floats" do
2414+
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 16"
2415+
2416+
assert_raise CompileError, message, fn ->
2417+
expand(quote(do: <<12.3::16>>))
2418+
end
2419+
end
2420+
2421+
test "raises for invalid size * unit for floats" do
2422+
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128"
2423+
2424+
assert_raise CompileError, message, fn ->
2425+
expand(quote(do: <<12.3::32*4>>))
2426+
end
2427+
2428+
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256"
2429+
2430+
assert_raise CompileError, message, fn ->
2431+
expand(quote(do: <<12.3::256>>))
2432+
end
24012433
end
24022434
end
24032435

0 commit comments

Comments
 (0)