git.iko.soy/rowtype-yoga/purescript-fixed-precision
on main help sign in
Fix String conversions with negatives #26 (#27)
ab302f2Harry Garrood
src/Data/Fixed.purs+16−5
@@ -44,6 +44,7 @@ import Data.BigInt as BigInt
44import Data.Int as Int
44import Data.Int as Int
45import Data.Maybe (Maybe(..), fromJust)
45import Data.Maybe (Maybe(..), fromJust)
46import Data.Monoid as Monoid
46import Data.Monoid as Monoid
47+import Data.Ord (abs)
47import Data.String.CodeUnits as StringCU
48import Data.String.CodeUnits as StringCU
48import Data.String as String
49import Data.String as String
49import Math as Math
50import Math as Math
@@ -344,12 +345,19 @@ fromString
344 . KnownPrecision precision
345 . KnownPrecision precision
345 => String
346 => String
346 -> Maybe (Fixed precision)
347 -> Maybe (Fixed precision)
347−fromString str =
348+fromString str' =
348 let
349 let
349 numDigits = reflectPrecisionDecimalPlaces (PProxy :: PProxy precision)
350 numDigits = reflectPrecisionDecimalPlaces (PProxy :: PProxy precision)
350 denom = reflectPrecision (PProxy :: PProxy precision)
351 denom = reflectPrecision (PProxy :: PProxy precision)
351
352
352 isDigit = between '0' '9'
353 isDigit = between '0' '9'
354+
355+ { sign, str } =
356+ case String.stripPrefix (String.Pattern "-") str' of
357+ Just str ->
358+ { sign: negate one, str }
359+ Nothing ->
360+ { sign: one, str: str' }
353 wholeDigits = StringCU.countPrefix isDigit str
361 wholeDigits = StringCU.countPrefix isDigit str
354 { before, after } = StringCU.splitAt wholeDigits str
362 { before, after } = StringCU.splitAt wholeDigits str
355
363
@@ -365,7 +373,7 @@ fromString str =
365 guard (StringCU.charAt 0 after == Just '.')
373 guard (StringCU.charAt 0 after == Just '.')
366 let raw = StringCU.drop 1 after
374 let raw = StringCU.drop 1 after
367 BigInt.fromString (rightJustify numDigits '0' raw)
375 BigInt.fromString (rightJustify numDigits '0' raw)
368− pure (Fixed (wholePart * denom + fractionPart))
376+ pure (Fixed (sign * (wholePart * denom + fractionPart)))
369
377
370-- | Represent a `Fixed` value as a string, with the given number of decimal
378-- | Represent a `Fixed` value as a string, with the given number of decimal
371-- | places.
379-- | places.
@@ -392,10 +400,13 @@ toStringWithPrecision requestedDigits fixed@(Fixed n) =
392 let
400 let
393 denom = denominator fixed
401 denom = denominator fixed
394 denomDigits = reflectPrecisionDecimalPlaces (PProxy :: PProxy precision)
402 denomDigits = reflectPrecisionDecimalPlaces (PProxy :: PProxy precision)
403+ absNum = abs n
404+ -- Use 'quot' and 'rem' because we need to round towards zero
395− wholePart = n / denom
405+ wholePart = BigInt.quot absNum denom
396− fractionalPart = n `mod` denom
406+ fractionalPart = BigInt.rem absNum denom
397 in
407 in
408+ (if n < zero then "-" else "")
398− BigInt.toString wholePart
409+ <> BigInt.toString wholePart
399 <> Monoid.guard (requestedDigits > 0)
410 <> Monoid.guard (requestedDigits > 0)
400 ("." <>
411 ("." <>
401 rightJustify requestedDigits '0'
412 rightJustify requestedDigits '0'
test/Main.purs+13−9
@@ -2,7 +2,7 @@ module Test.Main where
2
2
3import Prelude
3import Prelude
4
4
5−import Data.Fixed (class KnownPrecision, Fixed, PProxy, fromNumber, toNumber, fromString, reifyPrecision, reflectPrecision, toString, P100, rescale, fromBigInt, floor)
5+import Data.Fixed (class KnownPrecision, Fixed, PProxy, fromNumber, toNumber, fromString, reifyPrecision, reflectPrecision, toString, P100, rescale, fromBigInt, floor, ceil, numerator)
6import Data.BigInt as BigInt
6import Data.BigInt as BigInt
7import Data.Maybe (Maybe(..), fromJust, isJust)
7import Data.Maybe (Maybe(..), fromJust, isJust)
8import Effect (Effect)
8import Effect (Effect)
@@ -10,14 +10,17 @@ import Effect.Console (log)
10import Math as Math
10import Math as Math
11import Partial.Unsafe (unsafePartial)
11import Partial.Unsafe (unsafePartial)
12import Test.QuickCheck (arbitrary, quickCheck', (<?>), (===))
12import Test.QuickCheck (arbitrary, quickCheck', (<?>), (===))
13−import Test.QuickCheck.Gen (Gen, chooseInt, suchThat)
13+import Test.QuickCheck.Gen (Gen, chooseInt, uniform, suchThat)
14
14
15-- Just here to package up the safe use of `unsafePartial`.
15-- Just here to package up the safe use of `unsafePartial`.
16trying :: forall a b. Gen a -> (a -> Maybe b) -> Gen b
16trying :: forall a b. Gen a -> (a -> Maybe b) -> Gen b
17trying gen f = map (unsafePartial fromJust) (map f gen `suchThat` isJust)
17trying gen f = map (unsafePartial fromJust) (map f gen `suchThat` isJust)
18
18
19genFixedP :: forall p. KnownPrecision p => PProxy p -> Gen (Fixed p)
19genFixedP :: forall p. KnownPrecision p => PProxy p -> Gen (Fixed p)
20−genFixedP _ = map fromNumber arbitrary `trying` identity
20+genFixedP _ = map (fromNumber <<< stretch) uniform `trying` identity
21+ where
22+ -- maps [0,1] to [-10,10]
23+ stretch = (_ * 20.0) >>> (_ - 10.0)
21
24
22fromNumberWith
25fromNumberWith
23 :: forall precision
26 :: forall precision
@@ -35,15 +38,15 @@ withPrecisionOf
35 -> Fixed precision
38 -> Fixed precision
36withPrecisionOf _ x = x
39withPrecisionOf _ x = x
37
40
38−floorToPrecision
41+truncateToPrecision
39 :: forall precision
42 :: forall precision
40 . KnownPrecision precision
43 . KnownPrecision precision
41 => Int
44 => Int
42 -> Fixed precision
45 -> Fixed precision
43 -> Fixed precision
46 -> Fixed precision
44−floorToPrecision p =
47+truncateToPrecision p =
45 (_ / scaleFactor)
48 (_ / scaleFactor)
46− <<< floor
49+ <<< (\x -> if x > zero then floor x else ceil x)
47 <<< (_ * scaleFactor)
50 <<< (_ * scaleFactor)
48 where
51 where
49 scaleFactor =
52 scaleFactor =
@@ -123,7 +126,8 @@ main = do
123 precision <- chooseInt 0 100
126 precision <- chooseInt 0 100
124 unsafePartial fromJust $ reifyPrecision precision \p -> do
127 unsafePartial fromJust $ reifyPrecision precision \p -> do
125 a <- genFixedP p
128 a <- genFixedP p
126− pure $ fromString (toString a) == Just a <?> show { precision, a }
129+ let b = fromString (toString a)
130+ pure $ b == Just a <?> show { precision, a, b, numeratorA: numerator a, numeratorB: map numerator b }
127
131
128 log ""
132 log ""
129 log "Conversions to/from Number"
133 log "Conversions to/from Number"
@@ -162,7 +166,7 @@ main = do
162 let xx = rescale y
166 let xx = rescale y
163 pure $ x == xx <?> show { x, xx, y }
167 pure $ x == xx <?> show { x, xx, y }
164
168
165− log "rescaling down then up should be the same as flooring"
169+ log "rescaling down then up should be the same as truncating"
166 quickCheck' 1000 do
170 quickCheck' 1000 do
167 let hi = 20
171 let hi = 20
168 lo <- chooseInt 10 20
172 lo <- chooseInt 10 20
@@ -171,5 +175,5 @@ main = do
171 reifyPrecision hi \hi_ -> do
175 reifyPrecision hi \hi_ -> do
172 x <- genFixedP hi_
176 x <- genFixedP hi_
173 let y = withPrecisionOf lo_ $ rescale x
177 let y = withPrecisionOf lo_ $ rescale x
174− let xx = floorToPrecision lo x
178+ let xx = truncateToPrecision lo x
175 pure $ xx == rescale y <?> show { x, xx, y }
179 pure $ xx == rescale y <?> show { x, xx, y }