BeniaminoBaggins
BeniaminoBaggins

Reputation: 12483

Elixir - Parse string to float causing errors

I'm getting this error trying to parse a string to a float:

case insert_product_shop(conn, existing_product.id, existing_shop.id, String.to_float(posted_product["price"])) do

Error:

20:45:29.766 [error] #PID<0.342.0> running Api.Router terminated
Server: 172.20.10.2:4000 (http)
Request: POST /products
** (exit) an exception was raised:
    ** (ArgumentError) argument error
        :erlang.binary_to_float(58.25)
        (api) lib/api/router.ex:120: anonymous fn/1 in Api.Router.do_match/4
        (api) lib/api/router.ex:1: Api.Router.plug_builder_call/2
        (api) lib/plug/debugger.ex:123: Api.Router.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /Users/Ben/Development/Projects/vepo/api/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.exe
cute/4

When I change to

case insert_product_shop(conn, existing_product.id, existing_shop.id, Float.parse(posted_product["price"])) do
:

I get:

20:54:12.769 [error] #PID<0.336.0> running Api.Router terminated
Server: 172.20.10.2:4000 (http)
Request: POST /products
** (exit) an exception was raised:
    ** (ArgumentError) argument error
        :erlang.binary_to_float(24)
        (api) lib/api/router.ex:82: anonymous fn/1 in Api.Router.do_match/4
        (api) lib/api/router.ex:1: Api.Router.plug_builder_call/2
        (api) lib/plug/debugger.ex:123: Api.Router.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /Users/Ben/Development/Projects/vepo/api/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.exe
cute/4

How can I properly parse from string to float? Can I also allow it to be either a string or float and if it is string, parse to float?

Upvotes: 0

Views: 960

Answers (1)

Simone Carletti
Simone Carletti

Reputation: 176522

According to the error, it looks like the input is already a Float.

** (ArgumentError) argument error
    :erlang.binary_to_float(58.25)

In fact, String.to_float/1 raises an error if the input is not a String.

iex(1)> String.to_float(58.25)
** (ArgumentError) argument error
    :erlang.binary_to_float(58.25)
iex(1)> String.to_float("58.25")
58.25

Also note that String.to_float/1 also complains if the input has no decimal digits.

iex(2)> String.to_float("58")
** (ArgumentError) argument error
    :erlang.binary_to_float("58")
iex(2)> String.to_float("58.0")
58.0

You need to write a custom function. I'm not sure where posted_product["price"] is coming from, and whether effectively you expect it to have different input types. That may be a bug.

One possible workaround is to always cast the input to String, and use Float.parse/1

iex(12)> {f, _} = Float.parse(to_string("58.0"))
{58.0, ""}
iex(13)> f
58.0
iex(14)> {f, _} = Float.parse(to_string(58.0))
{58.0, ""}
iex(15)> f
58.0

Note that Float.parse/1 may return an :error, hence you must handle that.

Another option, perhaps slightly more efficient, is to use is_float/1 and is_string/1 to handle the conversion only if necessary.

defmodule Price do
  def to_float(input) when is_float(input) do
    input
  end
  def to_float(input) do
    case Float.parse(to_string(input)) do
      {f, ""} -> f
      _       -> :error
    end
  end
end

iex(2)> Price.to_float(32)
32.0
iex(3)> Price.to_float(32.0)
32.0
iex(4)> Price.to_float("32")
32.0

Upvotes: 4

Related Questions