anchor.diff:  
   nif_internal_hash(Config) ->
       ensure_lib_loaded(Config),
  -    Terms =
  -        [random_term() || _ <- lists:seq(1, 5000)],
  -
  -    % Unlike the phash2 hash, in this case we
  -    % have nothing to compare to, so let's try
  -    % and at least make sure the distribution
  -    % isn't outright wrong, statistical nuances
  -    % aside.
  -
  -    OnesPerBit =
  -        lists:foldl(
  -          fun (Term, Acc) ->
  -                  NifHashValue = hash_nif(internal, Term, 0),
  -                  lists:foldl(
  -                    fun (BitIndex, AccB) ->
  -                            BitValue = (NifHashValue band (1 bsl BitIndex)) bsr BitIndex,
  -                            dict:update_counter(BitIndex, BitValue, AccB)
  -                    end,
  -                    Acc,
  -                    lists:seq(0, 31))
  +    HashValueBitSize = nif_hash_result_bitsize(internal),
  +    Terms = unique([random_term() || _ <- lists:seq(1, 5000)]),
  +    HashValues = [hash_nif(internal, Term, 0) || Term <- Terms],
  +    test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05).
  +
  +nif_internal_hash_salted(Config) ->
  +    ensure_lib_loaded(Config),
  +    test_salted_nif_hash(internal).
  +
  +nif_phash2(Config) ->
  +    ensure_lib_loaded(Config),
  +    HashValueBitSize = nif_hash_result_bitsize(phash2),
  +    Terms = unique([random_term() || _ <- lists:seq(1, 5000)]),
  +    HashValues =
  +        lists:map(
  +          fun (Term) ->
  +                  HashValue = erlang:phash2(Term),
  +                  NifHashValue = hash_nif(phash2, Term, 0),
  +                  (HashValue =:= NifHashValue
  +                   orelse ct:fail("Expected: ~p\nActual:   ~p",
  +                                  [HashValue, NifHashValue])),
  +                  HashValue
  +          end,
  +          Terms),
  +    test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05).
  +
  +nif_phash2_salted(Config) ->
  +    ensure_lib_loaded(Config),
  +    test_salted_nif_hash(phash2).
  +
  +test_salted_nif_hash(HashType) ->
  +    HashValueBitSize = nif_hash_result_bitsize(HashType),
  +    Terms = unique([random_term() || _ <- lists:seq(1, 5000)]),
  +    Salts = unique([random_uint32() || _ <- lists:seq(1, 100)]),
  +    {HashValuesPerSalt, HashValuesPerTerm} =
  +        lists:mapfoldl(
  +          fun (Salt, Acc) ->
  +                  {HashValues, NewAcc} =
  +                    lists:mapfoldl(
  +                      fun (Term, AccB) ->
  +                              HashValue = hash_nif(HashType, Term, Salt),
  +                              NewAccB = dict:append(Term, HashValue, AccB),
  +                              {HashValue, NewAccB}
  +                      end,
  +                      Acc,
  +                      Terms),
  +                  {{Salt, HashValues}, NewAcc}
             end,
             dict:new(),
  -          Terms),
  -
  -    ExpectedNrOfOnes = length(Terms) div 2,
  +          Salts),
  +
  +    % Test per-salt hash distribution of different terms
  +    lists:foreach(
  +      fun ({_Salt, HashValues}) ->
  +              test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05)
  +      end,
  +      HashValuesPerSalt),
  +
  +    % Test per-term hash distribution of different salts
       dict:fold(
  -      fun (BitIndex, NrOfOnes, Acc) ->
  -              RelativeDeviation = abs(NrOfOnes - ExpectedNrOfOnes) / ExpectedNrOfOnes,
  -              (RelativeDeviation < 0.10
  -               orelse ct:fail("Unreasonable deviation on number of set bits (i=~p): "
  -                              "expected ~p, got ~p (relative dev. ~.3f)",
  -                              [BitIndex, ExpectedNrOfOnes, NrOfOnes, RelativeDeviation])),
  +      fun (_Term, HashValues, Acc) ->
  +              % Be more tolerant of relative deviation,
  +              % as there's fewer hash values here.
  +              test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.30),
                 Acc
         end,
         ok,
  -      OnesPerBit).
  -
  -nif_phash2(Config) ->
  -    ensure_lib_loaded(Config),
  -    Terms =
  -        [random_term() || _ <- lists:seq(1, 5000)],
  -
  -    lists:foreach(
  -      fun (Term) ->
  -              HashValue = erlang:phash2(Term),
  -              NifHashValue = hash_nif(phash2, Term, 0),
  -              (HashValue =:= NifHashValue
  -               orelse ct:fail("Expected: ~p\nActual:   ~p",
  -                              [HashValue, NifHashValue]))
  -      end,
  -      Terms).
  +      HashValuesPerTerm).
  +
  +test_bit_distribution_fitness(Integers, BitSize, MaxRelativeDeviation) ->
  +    MaxInteger = (1 bsl BitSize) - 1,
  +    OnesPerBit =
  +        lists:foldl(
  +          fun (Integer, Acc) when Integer >= 0, Integer =< MaxInteger ->
  +                  lists:foldl(
  +                    fun (BitIndex, AccB) ->
  +                            BitValue = (Integer band (1 bsl BitIndex)) bsr BitIndex,
  +                            orddict:update_counter(BitIndex, BitValue, AccB)
  +                    end,
  +                    Acc,
  +                    lists:seq(0, BitSize - 1))
  +          end,
  +          orddict:new(),
  +          Integers),
  +
  +    ExpectedNrOfOnes = length(Integers) div 2,
  +    FailureText =
  +        orddict:fold(
  +          fun (BitIndex, NrOfOnes, Acc) ->
  +                  RelativeDeviation = abs(NrOfOnes - ExpectedNrOfOnes) / length(Integers),
  +                  case RelativeDeviation >= MaxRelativeDeviation of
  +                      false -> Acc;
  +                      true ->
  +                          [Acc,
  +                           io_lib:format(
  +                             "Unreasonable deviation on number of set bits (i=~p): "
  +                             "expected ~p, got ~p (relative dev. ~.3f)~n",
  +                             [BitIndex, ExpectedNrOfOnes, NrOfOnes, RelativeDeviation])]
  +                  end
  +          end,
  +          [],
  +          OnesPerBit),
  +
  +    (FailureText =:= [] orelse ct:fail(FailureText)).
  +
  +nif_hash_result_bitsize(internal) -> 32;
  +nif_hash_result_bitsize(phash2) -> 27.
  +
  +unique(List) ->
  +    lists:usort(List).
  +
  +random_uint32() ->
  +    rand:uniform(1 bsl 32) - 1.
   
   random_term() ->
       case rand:uniform(6) of
  
positive.code:          nif_port_command/1,
           nif_snprintf/1,
           nif_internal_hash/1,
           nif_phash2/1
  	]).
  
  -export([many_args_100/100]).
  
negative.code:      nif_port_command,
       nif_snprintf,
       nif_internal_hash,
       nif_internal_hash_salted,
       nif_phash2,
       nif_phash2_salted].
  
  groups() ->
      [{G, [], api_repeaters()} || G <- api_groups()]
  