234 lines
6.4 KiB
Elixir
234 lines
6.4 KiB
Elixir
import Kernel, except: [round: 1]
|
|
|
|
defmodule Float do
|
|
@moduledoc """
|
|
Functions for working with floating point numbers.
|
|
"""
|
|
|
|
@doc """
|
|
Parses a binary into a float.
|
|
|
|
If successful, returns a tuple in the form of `{float, remainder_of_binary}`;
|
|
when the binary cannot be coerced into a valid float, the atom `:error` is
|
|
returned.
|
|
|
|
If the size of float exceeds the maximum size of `1.7976931348623157e+308`,
|
|
the `ArgumentError` exception is raised.
|
|
|
|
If you want to convert a string-formatted float directly to a float,
|
|
`String.to_float/1` can be used instead.
|
|
|
|
## Examples
|
|
|
|
iex> Float.parse("34")
|
|
{34.0, ""}
|
|
|
|
iex> Float.parse("34.25")
|
|
{34.25, ""}
|
|
|
|
iex> Float.parse("56.5xyz")
|
|
{56.5, "xyz"}
|
|
|
|
iex> Float.parse("pi")
|
|
:error
|
|
|
|
"""
|
|
@spec parse(binary) :: {float, binary} | :error
|
|
def parse("-" <> binary) do
|
|
case parse_unsigned(binary) do
|
|
:error -> :error
|
|
{number, remainder} -> {-number, remainder}
|
|
end
|
|
end
|
|
|
|
def parse("+" <> binary) do
|
|
parse_unsigned(binary)
|
|
end
|
|
|
|
def parse(binary) do
|
|
parse_unsigned(binary)
|
|
end
|
|
|
|
defp parse_unsigned(<<digit, rest::binary>>) when digit in ?0..?9, do:
|
|
parse_unsigned(rest, false, false, <<digit>>)
|
|
|
|
defp parse_unsigned(binary) when is_binary(binary), do:
|
|
:error
|
|
|
|
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9, do:
|
|
parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
|
|
|
|
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9, do:
|
|
parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
|
|
|
|
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and digit in ?0..?9, do:
|
|
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
|
|
|
|
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc) when exp_marker in 'eE' and sign in '-+' and digit in ?0..?9, do:
|
|
parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
|
|
|
|
defp parse_unsigned(rest, dot?, _e?, acc), do:
|
|
{:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
|
|
|
defp add_dot(acc, true), do: acc
|
|
defp add_dot(acc, false), do: acc <> ".0"
|
|
|
|
@doc """
|
|
Rounds a float to the largest integer less than or equal to `num`.
|
|
|
|
`floor/2` also accepts a precision to round a floating point value down
|
|
to an arbitrary number of fractional digits (between 0 and 15).
|
|
|
|
This function always returns a float. `Kernel.trunc/1` may be used instead to
|
|
truncate the result to an integer afterwards.
|
|
|
|
## Examples
|
|
|
|
iex> Float.floor(34.25)
|
|
34.0
|
|
|
|
iex> Float.floor(-56.5)
|
|
-57.0
|
|
|
|
iex> Float.floor(34.259, 2)
|
|
34.25
|
|
|
|
"""
|
|
@spec floor(float, 0..15) :: float
|
|
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
|
power = power_of_10(precision)
|
|
number = number * power
|
|
truncated = trunc(number)
|
|
variance = if number - truncated < 0, do: -1.0, else: 0.0
|
|
(truncated + variance) / power
|
|
end
|
|
|
|
@doc """
|
|
Rounds a float to the smallest integer greater than or equal to `num`.
|
|
|
|
`ceil/2` also accepts a precision to round a floating point value down
|
|
to an arbitrary number of fractional digits (between 0 and 15).
|
|
|
|
This function always returns floats. `Kernel.trunc/1` may be used instead to
|
|
truncate the result to an integer afterwards.
|
|
|
|
## Examples
|
|
|
|
iex> Float.ceil(34.25)
|
|
35.0
|
|
|
|
iex> Float.ceil(-56.5)
|
|
-56.0
|
|
|
|
iex> Float.ceil(34.251, 2)
|
|
34.26
|
|
|
|
"""
|
|
@spec ceil(float, 0..15) :: float
|
|
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
|
power = power_of_10(precision)
|
|
number = number * power
|
|
truncated = trunc(number)
|
|
variance = if number - truncated > 0, do: 1.0, else: 0.0
|
|
(truncated + variance) / power
|
|
end
|
|
|
|
@doc """
|
|
Rounds a floating point value to an arbitrary number of fractional digits
|
|
(between 0 and 15).
|
|
|
|
This function only accepts floats and always returns a float. Use
|
|
`Kernel.round/1` if you want a function that accepts both floats and integers
|
|
and always returns an integer.
|
|
|
|
## Examples
|
|
|
|
iex> Float.round(5.5674, 3)
|
|
5.567
|
|
|
|
iex> Float.round(5.5675, 3)
|
|
5.568
|
|
|
|
iex> Float.round(-5.5674, 3)
|
|
-5.567
|
|
|
|
iex> Float.round(-5.5675, 3)
|
|
-5.568
|
|
|
|
iex> Float.round(-5.5675)
|
|
-6.0
|
|
|
|
"""
|
|
@spec round(float, 0..15) :: float
|
|
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
|
power = power_of_10(precision)
|
|
Kernel.round(number * power) / power
|
|
end
|
|
|
|
Enum.reduce 0..15, 1, fn x, acc ->
|
|
defp power_of_10(unquote(x)), do: unquote(acc)
|
|
acc * 10
|
|
end
|
|
|
|
@doc """
|
|
Returns a charlist which corresponds to the text representation
|
|
of the given float.
|
|
|
|
It uses the shortest representation according to algorithm described
|
|
in "Printing Floating-Point Numbers Quickly and Accurately" in
|
|
Proceedings of the SIGPLAN '96 Conference on Programming Language
|
|
Design and Implementation.
|
|
|
|
## Examples
|
|
|
|
iex> Float.to_charlist(7.0)
|
|
'7.0'
|
|
|
|
"""
|
|
@spec to_charlist(float) :: charlist
|
|
def to_charlist(float) when is_float(float) do
|
|
:io_lib_format.fwrite_g(float)
|
|
end
|
|
|
|
@doc """
|
|
Returns a binary which corresponds to the text representation
|
|
of the given float.
|
|
|
|
It uses the shortest representation according to algorithm described
|
|
in "Printing Floating-Point Numbers Quickly and Accurately" in
|
|
Proceedings of the SIGPLAN '96 Conference on Programming Language
|
|
Design and Implementation.
|
|
|
|
## Examples
|
|
|
|
iex> Float.to_string(7.0)
|
|
"7.0"
|
|
|
|
"""
|
|
@spec to_string(float) :: String.t
|
|
def to_string(float) when is_float(float) do
|
|
IO.iodata_to_binary(:io_lib_format.fwrite_g(float))
|
|
end
|
|
|
|
# TODO: Deprecate by v1.5
|
|
@doc false
|
|
def to_char_list(float), do: Float.to_charlist(float)
|
|
|
|
# TODO: Deprecate by v1.4
|
|
@doc false
|
|
def to_char_list(float, options) do
|
|
:erlang.float_to_list(float, expand_compact(options))
|
|
end
|
|
|
|
# TODO: Deprecate by v1.4
|
|
@doc false
|
|
def to_string(float, options) do
|
|
:erlang.float_to_binary(float, expand_compact(options))
|
|
end
|
|
|
|
defp expand_compact([{:compact, false} | t]), do: expand_compact(t)
|
|
defp expand_compact([{:compact, true} | t]), do: [:compact | expand_compact(t)]
|
|
defp expand_compact([h | t]), do: [h | expand_compact(t)]
|
|
defp expand_compact([]), do: []
|
|
end
|