lib/float.ex

defmodule Modbus.Float do
  @moduledoc """
  IEEE754 float converter

  Based on https://www.h-schmidt.net/FloatConverter/Float.html.
  """

  @doc """
  Converts a list of 2n 16-bit registers to a list of IEEE754 big endian floats.

  ## Example

  ```elixir
  [+5.0] = Float.from_be([0x40a0, 0x0000])
  [-5.0, +5.0] = Float.from_be([0xc0a0, 0x0000, 0x40a0, 0x0000])
  ```
  """
  def from_be(list_of_regs), do: from(list_of_regs, :be)

  @doc """
  Converts a list of 2n 16-bit registers to a list of IEEE754 little endian floats.

  ## Example

  ```elixir
  [+5.0] = Float.from_le([0x0000, 0x40a0])
  [-5.0, +5.0] = Float.from_le([0x0000, 0xc0a0, 0x0000, 0x40a0])
  ```
  """
  def from_le(list_of_regs), do: from(list_of_regs, :le)

  @doc """
  Converts a list of IEEE754 big endian floats to a list of 2n 16-bit registers.

  ## Example

  ```elixir
  [0x40a0, 0x0000] = Float.to_be([+5.0])
  [0xc0a0, 0x0000, 0x40a0, 0x0000] = Float.to_be([-5.0, +5.0])
  ```
  """
  def to_be(list_of_floats), do: to(list_of_floats, :be)

  @doc """
  Converts a list of IEEE754 little endian floats to a list of 2n 16-bit registers.

  ## Example

  ```elixir
  [0x0000, 0x40a0] = Float.to_le([+5.0])
  [0x0000, 0xc0a0, 0x0000, 0x40a0] = Float.to_le([-5.0, +5.0])
  ```
  """
  def to_le(list_of_floats), do: to(list_of_floats, :le)

  defp from([], _), do: []

  defp from([w0, w1 | tail], endianness) do
    [from(w0, w1, endianness) | from(tail, endianness)]
  end

  defp from(w0, w1, :be) do
    <<value::float-32>> = <<w0::16, w1::16>>
    value
  end

  defp from(w0, w1, :le) do
    <<value::float-32>> = <<w1::16, w0::16>>
    value
  end

  defp to([], _), do: []

  defp to([f | tail], endianness) do
    [w0, w1] = to(f, endianness)
    [w0, w1 | to(tail, endianness)]
  end

  defp to(f, :be) do
    <<w0::16, w1::16>> = <<f::float-32>>
    [w0, w1]
  end

  defp to(f, :le) do
    <<w0::16, w1::16>> = <<f::float-32>>
    [w1, w0]
  end
end