Skip to content

Commit

Permalink
General refactor
Browse files Browse the repository at this point in the history
Most changes are related to readability and to removing formatInt
  • Loading branch information
cuducos committed Feb 11, 2017
1 parent 2ce2c0f commit 5ab4b75
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 180 deletions.
221 changes: 87 additions & 134 deletions src/FormatNumber.elm
Original file line number Diff line number Diff line change
@@ -1,172 +1,125 @@
module FormatNumber
exposing
( Locale
, formatFloat
, formatInt
, usLocale
, frenchLocale
, spanishLocale
)

{-| This simple package formats numbers as pretty strings. It is flexible
enough to deal with different number of decimals, different thousand
separators and diffetent decimal separator.
# Basic usage
>>> formatFloat {frenchLocale | decimals = 4} pi
"3,1416"
>>> formatInt usLocale 42042
"42,042"
>>> formatFloat spanishLocale e
"2,718"
module FormatNumber exposing (format)

>>> formatFloat {decimals=3, thousandSeparator="", decimalSeparator=","} 123456.789
"123456,789"
{-| This simple package formats `float` numbers as pretty strings. It is
flexible enough to deal with different number of decimals, different thousand
separators and diffetent decimal separator.
# Full documentation
## Locale
@docs Locale, usLocale , frenchLocale, spanishLocale
@docs format
## Number formatting
@docs formatFloat, formatInt
## What about `Int` numbers?
# Known bugs
>>> import Locales exposing (usLocale)
>>> format usLocale (toFloat 1234)
"1,234.00"
There are known bugs in how Elm handles large numbers:
>>> import Locales exposing (usLocale)
>>> format { usLocale | decimals = 0 } <| toFloat 1234
"1,234"
* https://github.com/elm-lang/elm-compiler/issues/264
* https://github.com/elm-lang/elm-compiler/issues/1246
## Known bugs
This library won't work with large numbers (over 2^31) until Elm itself is fixed:
There are [known](https://github.com/elm-lang/elm-compiler/issues/264)
[bugs](https://github.com/elm-lang/elm-compiler/issues/1246) in how Elm handles
large numbers. This library cannot work with large numbers (over `2 ^ 31`)
until Elm itself is fixed:
>>> formatFloat usLocale 1e10
>>> format usLocale 1e10
"1,410,065,408.00"
-}

import String
import Helpers exposing (..)


-- Locales


{-| Locale to configure the format options.
-}
type alias Locale =
{ decimals : Int
, thousandSeparator : String
, decimalSeparator : String
}



-- Locales from
-- https://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html


{-| Locale used in France, Canada, Finland, Sweden
It uses a non-breakable thin space (U+202F) as thousandSeparator.
>>> formatFloat frenchLocale 67295
"67 295,000"
-}
frenchLocale : Locale
frenchLocale =
Locale 3 "\x202F" ","


{-| locale used in the United States, Great Britain, and Thailand
>>> formatFloat usLocale 67295
"67,295.00"
-}
usLocale : Locale
usLocale =
Locale 2 "," "."

{-| locale used in Spain, Italy and Norway
>>> formatFloat spanishLocale 67295
"67.295,000"
-}
spanishLocale : Locale
spanishLocale =
Locale 3 "." ","



-- Functions
import Helpers
import Locales
import String


{-| Format a float number as a pretty string:
>>> formatFloat { decimals = 2, thousandSeparator = ",", decimalSeparator = "." } 1234.5567
>>> format { decimals = 2, thousandSeparator = ".", decimalSeparator = "," } 123456.789
"123.456,79"
>>> format { decimals = 2, thousandSeparator = ",", decimalSeparator = "." } 1234.5567
"1,234.56"
>>> formatFloat (Locale 3 "." ",") -7654.3210
>>> import Locales exposing (Locale)
>>> format (Locale 3 "." ",") -7654.3210
"−7.654,321"
>>> formatFloat (Locale 1 "," ".") -0.01
>>> import Locales exposing (Locale)
>>> format (Locale 1 "," ".") -0.01
"0.0"
>>> formatFloat (Locale 2 "," ".") 0.01
>>> import Locales exposing (Locale)
>>> format (Locale 2 "," ".") 0.01
"0.01"
>>> formatFloat (Locale 0 "," ".") 123.456
>>> import Locales exposing (Locale)
>>> format (Locale 0 "," ".") 123.456
"123"
>>> formatFloat (Locale 0 "," ".") 1e9
>>> import Locales exposing (Locale)
>>> format (Locale 0 "," ".") 1e9
"1,000,000,000"
>>> formatFloat (Locale 5 "," ".") 1.0
>>> import Locales exposing (Locale)
>>> format (Locale 5 "," ".") 1.0
"1.00000"
-}
formatFloat : Locale -> Float -> String
formatFloat locale num =
(formatInt locale (truncate num))
++ (separator locale)
++ (digits locale.decimals num)
>>> import Locales exposing (usLocale)
>>> format usLocale pi
"3.14"
{-| Format a integer number as a pretty string:
>>> formatInt { decimals = 1, thousandSeparator = ",", decimalSeparator = "." } 0
"0"
>>> formatInt (Locale 1 " " ".") 1234567890
"1 234 567 890"
>>> formatInt (Locale 10 "," ".") -123456
"−123,456"
-}
formatInt : Locale -> Int -> String
formatInt locale num =
case compare num 0 of
LT ->
formatInt locale (-num) |> String.cons '−'

EQ ->
"0"
>>> import Locales exposing (frenchLocale)
>>> format { frenchLocale | decimals = 4 } pi
"3,1416"
GT ->
splitThousands num |> String.join locale.thousandSeparator
>>> import Locales exposing (frenchLocale)
>>> format frenchLocale 67295
"67 295,000"
>>> import Locales exposing (spanishLocale)
>>> format spanishLocale e
"2,718"
{-| The separator, or ""
>>> import Locales exposing (spanishLocale)
>>> format spanishLocale 67295
"67.295,000"
>> separator (Locale 10 "," ".")
"."
>>> import Locales exposing (usLocale)
>>> format usLocale 67295
"67,295.00"
>> separator (Locale 0 "," ".")
""
-}
separator : Locale -> String
separator locale =
if locale.decimals == 0 then
""
else
locale.decimalSeparator
format : Locales.Locale -> Float -> String
format locale num =
let
truncated : Int
truncated =
truncate num

integers : String
integers =
case compare truncated 0 of
GT ->
truncated
|> Helpers.splitThousands
|> String.join locale.thousandSeparator

EQ ->
"0"

LT ->
-truncated
|> toFloat
|> format { locale | decimals = 0 }
|> String.cons '−'
in
if locale.decimals == 0 then
integers
else
String.concat
[ integers
, locale.decimalSeparator
, Helpers.decimals locale.decimals num
]
89 changes: 43 additions & 46 deletions src/Helpers.elm
Original file line number Diff line number Diff line change
@@ -1,64 +1,61 @@
module Helpers exposing (..)
module Helpers exposing (decimals, splitThousands)

{-| Module containing helper functions
@docs splitThousands, decimals
-}


{-| Returns the n first digits after the comma in a float
{-| Split a `Int` in `List String` grouping by thousands digits:
>>> splitThousands 12345
[ "12", "345" ]
>>> splitThousands 12
[ "12" ]
-}
splitThousands : Int -> List String
splitThousands num =
if num >= 1000 then
[ num % 1000 ]
|> List.map toString
|> List.map (String.padLeft 3 '0')
|> List.append (splitThousands <| num // 1000)
else
[ toString num ]

>>> digits 2 123.45
"45"

>>> digits 0 125
""
{-| Returns the first n decimal digits:
>>> digits 1 1.99
>>> decimals 2 123.45
"45"
>>> decimals 1 1.99
"0"
>>> digits 2 1.0
>>> decimals 2 1.0
"00"
>>> digits 2 -1.0001
"00"
>>> decimals 3 -1.0001
"000"
>>> digits 2 0.01
>>> decimals 2 0.01
"01"
>>> digits 2 0.10
>>> decimals 2 0.10
"10"
-}
digits : Int -> Float -> String
digits digits f =
let
multiplicator =
toFloat (10 ^ digits)

fint =
(round (f * multiplicator))
in
splitThousands fint
|> String.concat
|> String.right digits
|> String.padLeft digits '0'

{-| Recursive helper to format an integer
>>> splitThousands 12345
["12", "345"]
-}
splitThousands : Int -> List String
splitThousands number =
let
-- Helper recursive function.
-- Adds the last three digits of remainingNumber at the start of accumulator
splitRemaining : Int -> List String -> List String
splitRemaining remainingNumber accumulator =
if remainingNumber >= 10 ^ 3 then
splitRemaining
(remainingNumber // 10 ^ 3)
((remainingNumber % 10 ^ 3 |> toString |> String.padLeft 3 '0') :: accumulator)
else
(toString remainingNumber) :: accumulator
in
splitRemaining number []
decimals : Int -> Float -> String
decimals digits num =
digits
|> toFloat
|> (^) 10
|> (*) num
|> round
|> splitThousands
|> String.concat
|> String.right digits
|> String.padLeft digits '0'

0 comments on commit 5ab4b75

Please sign in to comment.