File 3331-binary-Polish-the-hex-encoding-and-decoding-function.patch of Package erlang

From bfb3c873bd1287149687b66f5c3098ddb7b1a11e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Wed, 15 Feb 2023 04:32:14 +0100
Subject: [PATCH] binary: Polish the hex encoding and decoding functions

The code for the `binary:encode_hex/1` and `binary:encode_hex/2`
functions has been simplified. The simplified implementation has a
more consistent runtime compared to the old implementation. To test
the performance, I made a new `hex_bench` benchmark based on the
`base64` benchmark:

https://gist.github.com/bjorng/522470f313ac01d16156a597657f97bb

For the old implementation, on my M1 Mac the results for the best case
and worst case are as follows:

    Size: 1024*1024 (divisible by 8)
    fun binary:encode_hex/1: 1000 iterations in 1899 ms: 526 it/sec

    Size: 1024*1024 + 1 (not divisible by 2 through 7)
    fun binary:encode_hex/1: 1000 iterations in 5984 ms: 167 it/sec

That is, the worst case is about three times slower than the best
case.

The simplifed encode function shows much less variation:

    Size: 1024*1024 (divisible by 8)
    fun binary:encode_hex/1: 1000 iterations in 1899 ms: 526 it/sec

    Size: 1024*1024 + 1 (not divisible by 8)
    fun binary:encode_hex/1: 1000 iterations in 2100 ms: 476 it/sec

For the old implementation of `decode_hex/1`, the benchmark results
are:

    fun binary:decode_hex/1: 1000 iterations in 28823 ms: 34 it/sec

The results for the simplified implementation are:

    Size: 1024*1024 (divisible by 8)
    fun binary:decode_hex/1: 1000 iterations in 3041 ms: 328 it/sec

    Size: 1024*1024+1 (not divisible by 8)
    fun binary:decode_hex/1: 1000 iterations in 3144 ms: 318 it/sec

That is, the simplified implementation is almost 10 times faster.

All hex functions now raise exceptions with correct stack traces (with
the function being called at the top of the stack trace) and error
information.
---
 lib/stdlib/src/binary.erl               | 141 ++++++++----------------
 lib/stdlib/src/erl_stdlib_errors.erl    |  11 +-
 lib/stdlib/test/binary_module_SUITE.erl |  33 +++++-
 3 files changed, 84 insertions(+), 101 deletions(-)

diff --git a/lib/stdlib/src/binary.erl b/lib/stdlib/src/binary.erl
index 4c27b1d3e6..e587cfe98d 100644
--- a/lib/stdlib/src/binary.erl
+++ b/lib/stdlib/src/binary.erl
@@ -365,13 +365,14 @@ get_opts_replace(_,_) ->
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %% Hex encoding functions
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--define(HEX(X, Off), (hex(X, OffSet)):16).
 -compile({inline, [hex/2]}).
 -spec encode_hex(Bin) -> Bin2 when
       Bin :: binary(),
       Bin2 :: <<_:_*16>>.
+encode_hex(Bin) when is_binary(Bin) ->
+    encode_hex(Bin, uppercase);
 encode_hex(Bin) ->
-    encode_hex(Bin, uppercase).
+    error_with_info(badarg, [Bin]).
 
 -spec encode_hex(Bin, Case) -> Bin2 when
       Bin :: binary(),
@@ -384,23 +385,17 @@ encode_hex(Bin, lowercase) when is_binary(Bin) ->
 encode_hex(Bin, Case) ->
     error_with_info(badarg, [Bin, Case]).
 
--spec encode_hex1(binary(), 1 | 257) -> <<_:_*16>>.
-encode_hex1(Data, OffSet) when byte_size(Data) rem 8 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet),?HEX(C, OffSet),?HEX(D, OffSet),?HEX(E, OffSet),?HEX(F, OffSet),?HEX(G, OffSet),?HEX(H, OffSet)>> || <<A,B,C,D,E,F,G,H>> <= Data >>;
-encode_hex1(Data, OffSet) when byte_size(Data) rem 7 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet),?HEX(C, OffSet),?HEX(D, OffSet),?HEX(E, OffSet),?HEX(F, OffSet),?HEX(G, OffSet)>> || <<A,B,C,D,E,F,G>> <= Data >>;
-encode_hex1(Data, OffSet) when byte_size(Data) rem 6 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet),?HEX(C, OffSet),?HEX(D, OffSet),?HEX(E, OffSet),?HEX(F, OffSet)>> || <<A,B,C,D,E,F>> <= Data >>;
-encode_hex1(Data, OffSet) when byte_size(Data) rem 5 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet),?HEX(C, OffSet),?HEX(D, OffSet),?HEX(E, OffSet)>> || <<A,B,C,D,E>> <= Data >>;
-encode_hex1(Data, OffSet) when byte_size(Data) rem 4 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet),?HEX(C, OffSet),?HEX(D, OffSet)>> || <<A,B,C,D>> <= Data >>;
-encode_hex1(Data, OffSet) when byte_size(Data) rem 3 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet),?HEX(C, OffSet)>> || <<A,B,C>> <= Data >>;
-encode_hex1(Data, OffSet) when byte_size(Data) rem 2 =:= 0 ->
-    << <<?HEX(A, OffSet),?HEX(B, OffSet)>> || <<A,B>> <= Data >>;
-encode_hex1(Data, OffSet) when is_binary(Data) ->
-    << <<?HEX(N, OffSet)>> || <<N>> <= Data >>.
+encode_hex1(Data, Offset) ->
+    <<First:(bit_size(Data) div 64)/binary-unit:64, Rest/binary>> = Data,
+    Hex = << <<(hex(A, Offset)):16, (hex(B, Offset)):16, (hex(C, Offset)):16, (hex(D, Offset)):16,
+               (hex(E, Offset)):16, (hex(F, Offset)):16, (hex(G, Offset)):16, (hex(H, Offset)):16>> ||
+              <<A,B,C,D,E,F,G,H>> <= First >>,
+    encode_hex2(Rest, Offset, Hex).
+
+encode_hex2(<<A,Data/binary>>, Offset, Acc) ->
+    encode_hex2(Data, Offset, <<Acc/binary, (hex(A, Offset)):16>>);
+encode_hex2(<<>>, _Offset, Acc) ->
+    Acc.
 
 hex(X, Offset) ->
     element(
@@ -440,84 +435,42 @@ hex(X, Offset) ->
                    16#6530, 16#6531, 16#6532, 16#6533, 16#6534, 16#6535, 16#6536, 16#6537, 16#6538, 16#6539, 16#6561, 16#6562, 16#6563, 16#6564, 16#6565, 16#6566,
                    16#6630, 16#6631, 16#6632, 16#6633, 16#6634, 16#6635, 16#6636, 16#6637, 16#6638, 16#6639, 16#6661, 16#6662, 16#6663, 16#6664, 16#6665, 16#6666}).
 
+-compile({inline, [unhex/1]}).
 -spec decode_hex(Bin) -> Bin2 when
       Bin :: <<_:_*16>>,
       Bin2 :: binary().
-decode_hex(Bin) when byte_size(Bin) rem 2 =:= 0 ->
-    << <<(unhex(Int))>> || <<Int:16>> <= Bin >>;
-decode_hex(Bin) ->
-    badarg_with_info([Bin]).
-
-%% This function pattern-matches on the hexadecimal representation of a pair of characters
-%% for example, 16#3030 is matching on the integers <<48, 48>>, which is ascii for <<"00">>
-unhex(16#3030) -> 0; unhex(16#3031) -> 1; unhex(16#3032) -> 2; unhex(16#3033) -> 3; unhex(16#3034) -> 4; unhex(16#3035) -> 5; unhex(16#3036) -> 6; unhex(16#3037) -> 7; unhex(16#3038) -> 8; unhex(16#3039) -> 9;
-unhex(16#3041) -> 10; unhex(16#3042) -> 11; unhex(16#3043) -> 12; unhex(16#3044) -> 13; unhex(16#3045) -> 14; unhex(16#3046) -> 15;
-unhex(16#3061) -> 10; unhex(16#3062) -> 11; unhex(16#3063) -> 12; unhex(16#3064) -> 13; unhex(16#3065) -> 14; unhex(16#3066) -> 15;
-unhex(16#3130) -> 16; unhex(16#3131) -> 17; unhex(16#3132) -> 18; unhex(16#3133) -> 19; unhex(16#3134) -> 20; unhex(16#3135) -> 21; unhex(16#3136) -> 22; unhex(16#3137) -> 23; unhex(16#3138) -> 24; unhex(16#3139) -> 25;
-unhex(16#3141) -> 26; unhex(16#3142) -> 27; unhex(16#3143) -> 28; unhex(16#3144) -> 29; unhex(16#3145) -> 30; unhex(16#3146) -> 31;
-unhex(16#3161) -> 26; unhex(16#3162) -> 27; unhex(16#3163) -> 28; unhex(16#3164) -> 29; unhex(16#3165) -> 30; unhex(16#3166) -> 31;
-unhex(16#3230) -> 32; unhex(16#3231) -> 33; unhex(16#3232) -> 34; unhex(16#3233) -> 35; unhex(16#3234) -> 36; unhex(16#3235) -> 37; unhex(16#3236) -> 38; unhex(16#3237) -> 39; unhex(16#3238) -> 40; unhex(16#3239) -> 41;
-unhex(16#3241) -> 42; unhex(16#3242) -> 43; unhex(16#3243) -> 44; unhex(16#3244) -> 45; unhex(16#3245) -> 46; unhex(16#3246) -> 47;
-unhex(16#3261) -> 42; unhex(16#3262) -> 43; unhex(16#3263) -> 44; unhex(16#3264) -> 45; unhex(16#3265) -> 46; unhex(16#3266) -> 47;
-unhex(16#3330) -> 48; unhex(16#3331) -> 49; unhex(16#3332) -> 50; unhex(16#3333) -> 51; unhex(16#3334) -> 52; unhex(16#3335) -> 53; unhex(16#3336) -> 54; unhex(16#3337) -> 55; unhex(16#3338) -> 56; unhex(16#3339) -> 57;
-unhex(16#3341) -> 58; unhex(16#3342) -> 59; unhex(16#3343) -> 60; unhex(16#3344) -> 61; unhex(16#3345) -> 62; unhex(16#3346) -> 63;
-unhex(16#3361) -> 58; unhex(16#3362) -> 59; unhex(16#3363) -> 60; unhex(16#3364) -> 61; unhex(16#3365) -> 62; unhex(16#3366) -> 63;
-unhex(16#3430) -> 64; unhex(16#3431) -> 65; unhex(16#3432) -> 66; unhex(16#3433) -> 67; unhex(16#3434) -> 68; unhex(16#3435) -> 69; unhex(16#3436) -> 70; unhex(16#3437) -> 71; unhex(16#3438) -> 72; unhex(16#3439) -> 73;
-unhex(16#3441) -> 74; unhex(16#3442) -> 75; unhex(16#3443) -> 76; unhex(16#3444) -> 77; unhex(16#3445) -> 78; unhex(16#3446) -> 79;
-unhex(16#3461) -> 74; unhex(16#3462) -> 75; unhex(16#3463) -> 76; unhex(16#3464) -> 77; unhex(16#3465) -> 78; unhex(16#3466) -> 79;
-unhex(16#3530) -> 80; unhex(16#3531) -> 81; unhex(16#3532) -> 82; unhex(16#3533) -> 83; unhex(16#3534) -> 84; unhex(16#3535) -> 85; unhex(16#3536) -> 86; unhex(16#3537) -> 87; unhex(16#3538) -> 88; unhex(16#3539) -> 89;
-unhex(16#3541) -> 90; unhex(16#3542) -> 91; unhex(16#3543) -> 92; unhex(16#3544) -> 93; unhex(16#3545) -> 94; unhex(16#3546) -> 95;
-unhex(16#3561) -> 90; unhex(16#3562) -> 91; unhex(16#3563) -> 92; unhex(16#3564) -> 93; unhex(16#3565) -> 94; unhex(16#3566) -> 95;
-unhex(16#3630) -> 96; unhex(16#3631) -> 97; unhex(16#3632) -> 98; unhex(16#3633) -> 99; unhex(16#3634) -> 100; unhex(16#3635) -> 101; unhex(16#3636) -> 102; unhex(16#3637) -> 103; unhex(16#3638) -> 104; unhex(16#3639) -> 105;
-unhex(16#3641) -> 106; unhex(16#3642) -> 107; unhex(16#3643) -> 108; unhex(16#3644) -> 109; unhex(16#3645) -> 110; unhex(16#3646) -> 111;
-unhex(16#3661) -> 106; unhex(16#3662) -> 107; unhex(16#3663) -> 108; unhex(16#3664) -> 109; unhex(16#3665) -> 110; unhex(16#3666) -> 111;
-unhex(16#3730) -> 112; unhex(16#3731) -> 113; unhex(16#3732) -> 114; unhex(16#3733) -> 115; unhex(16#3734) -> 116; unhex(16#3735) -> 117; unhex(16#3736) -> 118; unhex(16#3737) -> 119; unhex(16#3738) -> 120; unhex(16#3739) -> 121;
-unhex(16#3741) -> 122; unhex(16#3742) -> 123; unhex(16#3743) -> 124; unhex(16#3744) -> 125; unhex(16#3745) -> 126; unhex(16#3746) -> 127;
-unhex(16#3761) -> 122; unhex(16#3762) -> 123; unhex(16#3763) -> 124; unhex(16#3764) -> 125; unhex(16#3765) -> 126; unhex(16#3766) -> 127;
-unhex(16#3830) -> 128; unhex(16#3831) -> 129; unhex(16#3832) -> 130; unhex(16#3833) -> 131; unhex(16#3834) -> 132; unhex(16#3835) -> 133; unhex(16#3836) -> 134; unhex(16#3837) -> 135; unhex(16#3838) -> 136; unhex(16#3839) -> 137;
-unhex(16#3841) -> 138; unhex(16#3842) -> 139; unhex(16#3843) -> 140; unhex(16#3844) -> 141; unhex(16#3845) -> 142; unhex(16#3846) -> 143;
-unhex(16#3861) -> 138; unhex(16#3862) -> 139; unhex(16#3863) -> 140; unhex(16#3864) -> 141; unhex(16#3865) -> 142; unhex(16#3866) -> 143;
-unhex(16#3930) -> 144; unhex(16#3931) -> 145; unhex(16#3932) -> 146; unhex(16#3933) -> 147; unhex(16#3934) -> 148; unhex(16#3935) -> 149; unhex(16#3936) -> 150; unhex(16#3937) -> 151; unhex(16#3938) -> 152; unhex(16#3939) -> 153;
-unhex(16#3941) -> 154; unhex(16#3942) -> 155; unhex(16#3943) -> 156; unhex(16#3944) -> 157; unhex(16#3945) -> 158; unhex(16#3946) -> 159;
-unhex(16#3961) -> 154; unhex(16#3962) -> 155; unhex(16#3963) -> 156; unhex(16#3964) -> 157; unhex(16#3965) -> 158; unhex(16#3966) -> 159;
-unhex(16#4130) -> 160; unhex(16#4131) -> 161; unhex(16#4132) -> 162; unhex(16#4133) -> 163; unhex(16#4134) -> 164; unhex(16#4135) -> 165; unhex(16#4136) -> 166; unhex(16#4137) -> 167; unhex(16#4138) -> 168; unhex(16#4139) -> 169;
-unhex(16#4141) -> 170; unhex(16#4142) -> 171; unhex(16#4143) -> 172; unhex(16#4144) -> 173; unhex(16#4145) -> 174; unhex(16#4146) -> 175;
-unhex(16#4161) -> 170; unhex(16#4162) -> 171; unhex(16#4163) -> 172; unhex(16#4164) -> 173; unhex(16#4165) -> 174; unhex(16#4166) -> 175;
-unhex(16#4230) -> 176; unhex(16#4231) -> 177; unhex(16#4232) -> 178; unhex(16#4233) -> 179; unhex(16#4234) -> 180; unhex(16#4235) -> 181; unhex(16#4236) -> 182; unhex(16#4237) -> 183; unhex(16#4238) -> 184; unhex(16#4239) -> 185;
-unhex(16#4241) -> 186; unhex(16#4242) -> 187; unhex(16#4243) -> 188; unhex(16#4244) -> 189; unhex(16#4245) -> 190; unhex(16#4246) -> 191;
-unhex(16#4261) -> 186; unhex(16#4262) -> 187; unhex(16#4263) -> 188; unhex(16#4264) -> 189; unhex(16#4265) -> 190; unhex(16#4266) -> 191;
-unhex(16#4330) -> 192; unhex(16#4331) -> 193; unhex(16#4332) -> 194; unhex(16#4333) -> 195; unhex(16#4334) -> 196; unhex(16#4335) -> 197; unhex(16#4336) -> 198; unhex(16#4337) -> 199; unhex(16#4338) -> 200; unhex(16#4339) -> 201;
-unhex(16#4341) -> 202; unhex(16#4342) -> 203; unhex(16#4343) -> 204; unhex(16#4344) -> 205; unhex(16#4345) -> 206; unhex(16#4346) -> 207;
-unhex(16#4361) -> 202; unhex(16#4362) -> 203; unhex(16#4363) -> 204; unhex(16#4364) -> 205; unhex(16#4365) -> 206; unhex(16#4366) -> 207;
-unhex(16#4430) -> 208; unhex(16#4431) -> 209; unhex(16#4432) -> 210; unhex(16#4433) -> 211; unhex(16#4434) -> 212; unhex(16#4435) -> 213; unhex(16#4436) -> 214; unhex(16#4437) -> 215; unhex(16#4438) -> 216; unhex(16#4439) -> 217;
-unhex(16#4441) -> 218; unhex(16#4442) -> 219; unhex(16#4443) -> 220; unhex(16#4444) -> 221; unhex(16#4445) -> 222; unhex(16#4446) -> 223;
-unhex(16#4461) -> 218; unhex(16#4462) -> 219; unhex(16#4463) -> 220; unhex(16#4464) -> 221; unhex(16#4465) -> 222; unhex(16#4466) -> 223;
-unhex(16#4530) -> 224; unhex(16#4531) -> 225; unhex(16#4532) -> 226; unhex(16#4533) -> 227; unhex(16#4534) -> 228; unhex(16#4535) -> 229; unhex(16#4536) -> 230; unhex(16#4537) -> 231; unhex(16#4538) -> 232; unhex(16#4539) -> 233;
-unhex(16#4541) -> 234; unhex(16#4542) -> 235; unhex(16#4543) -> 236; unhex(16#4544) -> 237; unhex(16#4545) -> 238; unhex(16#4546) -> 239;
-unhex(16#4561) -> 234; unhex(16#4562) -> 235; unhex(16#4563) -> 236; unhex(16#4564) -> 237; unhex(16#4565) -> 238; unhex(16#4566) -> 239;
-unhex(16#4630) -> 240; unhex(16#4631) -> 241; unhex(16#4632) -> 242; unhex(16#4633) -> 243; unhex(16#4634) -> 244; unhex(16#4635) -> 245; unhex(16#4636) -> 246; unhex(16#4637) -> 247; unhex(16#4638) -> 248; unhex(16#4639) -> 249;
-unhex(16#4641) -> 250; unhex(16#4642) -> 251; unhex(16#4643) -> 252; unhex(16#4644) -> 253; unhex(16#4645) -> 254; unhex(16#4646) -> 255;
-unhex(16#4661) -> 250; unhex(16#4662) -> 251; unhex(16#4663) -> 252; unhex(16#4664) -> 253; unhex(16#4665) -> 254; unhex(16#4666) -> 255;
-unhex(16#6130) -> 160; unhex(16#6131) -> 161; unhex(16#6132) -> 162; unhex(16#6133) -> 163; unhex(16#6134) -> 164; unhex(16#6135) -> 165; unhex(16#6136) -> 166; unhex(16#6137) -> 167; unhex(16#6138) -> 168; unhex(16#6139) -> 169;
-unhex(16#6141) -> 170; unhex(16#6142) -> 171; unhex(16#6143) -> 172; unhex(16#6144) -> 173; unhex(16#6145) -> 174; unhex(16#6146) -> 175;
-unhex(16#6161) -> 170; unhex(16#6162) -> 171; unhex(16#6163) -> 172; unhex(16#6164) -> 173; unhex(16#6165) -> 174; unhex(16#6166) -> 175;
-unhex(16#6230) -> 176; unhex(16#6231) -> 177; unhex(16#6232) -> 178; unhex(16#6233) -> 179; unhex(16#6234) -> 180; unhex(16#6235) -> 181; unhex(16#6236) -> 182; unhex(16#6237) -> 183; unhex(16#6238) -> 184; unhex(16#6239) -> 185;
-unhex(16#6241) -> 186; unhex(16#6242) -> 187; unhex(16#6243) -> 188; unhex(16#6244) -> 189; unhex(16#6245) -> 190; unhex(16#6246) -> 191;
-unhex(16#6261) -> 186; unhex(16#6262) -> 187; unhex(16#6263) -> 188; unhex(16#6264) -> 189; unhex(16#6265) -> 190; unhex(16#6266) -> 191;
-unhex(16#6330) -> 192; unhex(16#6331) -> 193; unhex(16#6332) -> 194; unhex(16#6333) -> 195; unhex(16#6334) -> 196; unhex(16#6335) -> 197; unhex(16#6336) -> 198; unhex(16#6337) -> 199; unhex(16#6338) -> 200; unhex(16#6339) -> 201;
-unhex(16#6341) -> 202; unhex(16#6342) -> 203; unhex(16#6343) -> 204; unhex(16#6344) -> 205; unhex(16#6345) -> 206; unhex(16#6346) -> 207;
-unhex(16#6361) -> 202; unhex(16#6362) -> 203; unhex(16#6363) -> 204; unhex(16#6364) -> 205; unhex(16#6365) -> 206; unhex(16#6366) -> 207;
-unhex(16#6430) -> 208; unhex(16#6431) -> 209; unhex(16#6432) -> 210; unhex(16#6433) -> 211; unhex(16#6434) -> 212; unhex(16#6435) -> 213; unhex(16#6436) -> 214; unhex(16#6437) -> 215; unhex(16#6438) -> 216; unhex(16#6439) -> 217;
-unhex(16#6441) -> 218; unhex(16#6442) -> 219; unhex(16#6443) -> 220; unhex(16#6444) -> 221; unhex(16#6445) -> 222; unhex(16#6446) -> 223;
-unhex(16#6461) -> 218; unhex(16#6462) -> 219; unhex(16#6463) -> 220; unhex(16#6464) -> 221; unhex(16#6465) -> 222; unhex(16#6466) -> 223;
-unhex(16#6530) -> 224; unhex(16#6531) -> 225; unhex(16#6532) -> 226; unhex(16#6533) -> 227; unhex(16#6534) -> 228; unhex(16#6535) -> 229; unhex(16#6536) -> 230; unhex(16#6537) -> 231; unhex(16#6538) -> 232; unhex(16#6539) -> 233;
-unhex(16#6541) -> 234; unhex(16#6542) -> 235; unhex(16#6543) -> 236; unhex(16#6544) -> 237; unhex(16#6545) -> 238; unhex(16#6546) -> 239;
-unhex(16#6561) -> 234; unhex(16#6562) -> 235; unhex(16#6563) -> 236; unhex(16#6564) -> 237; unhex(16#6565) -> 238; unhex(16#6566) -> 239;
-unhex(16#6630) -> 240; unhex(16#6631) -> 241; unhex(16#6632) -> 242; unhex(16#6633) -> 243; unhex(16#6634) -> 244; unhex(16#6635) -> 245; unhex(16#6636) -> 246; unhex(16#6637) -> 247; unhex(16#6638) -> 248; unhex(16#6639) -> 249;
-unhex(16#6641) -> 250; unhex(16#6642) -> 251; unhex(16#6643) -> 252; unhex(16#6644) -> 253; unhex(16#6645) -> 254; unhex(16#6646) -> 255;
-unhex(16#6661) -> 250; unhex(16#6662) -> 251; unhex(16#6663) -> 252; unhex(16#6664) -> 253; unhex(16#6665) -> 254; unhex(16#6666) -> 255;
-unhex(Char) ->
-    badarg_with_info([<<Char:16>>]).
+decode_hex(Data) when byte_size(Data) rem 2 =:= 0 ->
+    try
+        decode_hex1(Data)
+    catch
+        error:badarg ->
+            badarg_with_info([Data])
+    end;
+decode_hex(Data) ->
+    badarg_with_info([Data]).
+
+decode_hex1(Data) ->
+    <<First:(byte_size(Data) div 8)/binary-unit:64, Rest/binary>> = Data,
+    Bin = << <<(unhex(A)):4, (unhex(B)):4, (unhex(C)):4, (unhex(D)):4,
+               (unhex(E)):4, (unhex(F)):4, (unhex(G)):4, (unhex(H)):4>> ||
+              <<A,B,C,D,E,F,G,H>> <= First >>,
+    decode_hex2(Rest, Bin).
+
+decode_hex2(<<A,Data/binary>>, Acc) ->
+    decode_hex2(Data, <<Acc/binary-unit:4, (unhex(A)):4>>);
+decode_hex2(<<>>, Acc) ->
+    Acc.
+
+unhex(X) ->
+    element(X,
+            {nonono, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %1
+             no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %16
+             no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %32
+              0,  1,  2,  3,  4,  5,  6,  7,  8,  9, no, no, no, no, no, no, %48
+             no, 10, 11, 12, 13, 14, 15, no, no, no, no, no, no, no, no, no, %64
+             no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, %80
+             no, 10, 11, 12, 13, 14, 15, no, no, no, no, no, no, no, no, no  %96
+            }).
 
 badarg_with_cause(Args, Cause) ->
     erlang:error(badarg, Args, [{error_info, #{module => erl_stdlib_errors,
diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl
index 540c195a2a..a90d6477a7 100644
--- a/lib/stdlib/src/erl_stdlib_errors.erl
+++ b/lib/stdlib/src/erl_stdlib_errors.erl
@@ -77,8 +77,13 @@ format_binary_error(encode_hex, [Subject, Case], _) ->
     [must_be_binary(Subject), must_be_hex_case(Case)];
 format_binary_error(decode_hex, [Subject], _) ->
     if
-        is_binary(Subject), byte_size(Subject) rem 2 == 1 ->
-            ["must contain an even number of bytes"];
+        is_binary(Subject) ->
+            if
+                byte_size(Subject) rem 2 =:= 1 ->
+                    [<<"must contain an even number of bytes">>];
+                true ->
+                    [<<"must only contain hex digits 0-9, A-F, and a-f">>]
+            end;
         true ->
             [must_be_binary(Subject)]
     end;
@@ -1088,7 +1093,7 @@ expand_error(not_atom) ->
 expand_error(not_binary) ->
     <<"not a binary">>;
 expand_error(bad_hex_case) ->
-    <<"not uppercase or lowercase">>;
+    <<"not 'uppercase' or 'lowercase'">>;
 expand_error(not_compiled_regexp) ->
     <<"not a compiled regular expression">>;
 expand_error(not_iodata) ->
diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl
index 5ed05bfc55..954efae9b7 100644
--- a/lib/stdlib/test/binary_module_SUITE.erl
+++ b/lib/stdlib/test/binary_module_SUITE.erl
@@ -1462,11 +1462,15 @@ error_info(_Config) ->
          {split, [<<1,2,3>>, {bm,make_ref()}, []]},
          {split, [<<1,2,3>>, <<"2">>, [bad_option]]},
 
-         {encode_hex, [{no,a,binary}], [allow_rename]},
+         {encode_hex, [{no,binary}]},
+         {encode_hex, [{no,binary}, lowercase]},
          {encode_hex, [<<"foobar">>, othercase]},
-         {decode_hex, [{no,a,binary}]},
-         {decode_hex, [<<"000">>],[allow_rename]},
-         {decode_hex, [<<"GG">>],[allow_rename]}
+         {encode_hex, [no_binary, othercase], [{1,".*"}, {2,".*"}]},
+
+         {decode_hex, [{no,binary}]},
+         {decode_hex, [<<"000">>]},
+         {decode_hex, [<<"GG">>]},
+         {decode_hex, [<<255>>]}
         ],
     error_info_lib:test_error_info(binary, L).
 
@@ -1512,8 +1516,29 @@ hex_encoding(Config) when is_list(Config) ->
     <<"foobar">> = binary:decode_hex(<<"666f6f626172">>),
 
     <<"foobar">> = binary:decode_hex(<<"666f6F626172">>),
+
+    rand:seed(default),
+    io:format("*** SEED: ~p ***\n", [rand:export_seed()]),
+    Bytes = iolist_to_binary([rand:bytes(256), lists:seq(0, 255)]),
+    do_hex_roundtrip(Bytes),
+
     ok.
 
+do_hex_roundtrip(Bytes) ->
+    UpperHex = binary:encode_hex(Bytes),
+    UpperHex = binary:encode_hex(Bytes, uppercase),
+    LowerHex = binary:encode_hex(Bytes, lowercase),
+
+    Bytes = binary:decode_hex(UpperHex),
+    Bytes = binary:decode_hex(LowerHex),
+
+    case Bytes of
+        <<_, Rest/binary>> ->
+            do_hex_roundtrip(Rest);
+        <<>> ->
+            ok
+    end.
+
 %%%
 %%% Utilities.
 %%%
-- 
2.35.3

openSUSE Build Service is sponsored by