Search code examples
parsingfloating-pointnumbersdecimalclojurescript

Parse decimal number using Cuerdas in a ClojureScript program


I am using Cuerdas library in a ClojureScript program to parse floating point numbers. Sometimes the input from a user is missing the integral part, like .1 for 0.1. I need to handle this. But I ran into strange results when comparing the parsed numbers:

(= 0.1 (parse-number "0.1"))
;; => true

(= .1 (parse-number "0.1"))
;; => true

(= 0.1 (parse-number ".1"))
;; => false

(= .1 (parse-number ".1"))
;; => false

The last two results above are surprising to me. When comparing literal number I get the expected result:

(= 0 .0)
;; => true

The strange behavior is also visible when I just parse the decimal number without integer part, like here:

(parse-number ".1")
;; => .1

.1
;; => 0.1

I found out that I can use js/parseFloat to get what I want, but I'm curious what is the reason for this behavior when using Cuerdas? What is this .1 value and how is it different than 0.1?


Solution

  • parse-number source looked like this:

    (defn parse-number
      "General purpose function for parse number like
      string to number. It works with both integers
      and floats."
      [s]
      (if (nil? s)
        #?(:cljs js/NaN :clj Double/NaN)
        (if (numeric? s)
          (edn/read-string s)
          #?(:cljs js/NaN :clj Double/NaN))))
    

    Both ".1" and "0.1" are considered to be numeric? (was implemented in cuerdas by a regex check):

    cljs.user=> (def re #"^[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?$")
    #'cljs.user/re
    cljs.user=> (boolean (re-matches re "0.1"))
    true
    cljs.user=> (boolean (re-matches re ".1"))
    true
    

    so they will be read by cljs.reader/read-string.

    When reading the string "0.1" the return type is a js/Number, but ".1" is of type cljs.core/Symbol:

    cljs.user=> (cljs.reader/read-string ".1")
    .1
    cljs.user=> (cljs.reader/read-string "0.1")
    0.1
    cljs.user=> (type (cljs.reader/read-string "0.1"))
    #object[Number]
    cljs.user=> (type (cljs.reader/read-string ".1"))
    cljs.core/Symbol
    

    So although it looks like it correctly parsed the .1 it actually turned it into a symbol. The symbol .1 is not equal to the number .1.

    Note that parse-number is no longer available in newer versions of cuerdas because it "is a string manipulation library not a numbers parsing library".