Price fraction

From Rosetta Code
Revision as of 19:48, 18 March 2010 by rosettacode>DanBron (→‎{{header|J}}: more line up)
Task
Price fraction
You are encouraged to solve this task according to the task description, using any language you may know.

A friend of mine runs a Pharmacy. He has a specialised rounding function in his Dispensary application which receives a decimal value of currency and forces it to a standard value. This value is regulated by a government department.

Task: Given a floating point value between 0.00 and 1.00, rescale according to the following table:

>=  0.00  <  0.06  :=  0.10
>=  0.06  <  0.11  :=  0.18
>=  0.11  <  0.16  :=  0.26
>=  0.16  <  0.21  :=  0.32
>=  0.21  <  0.26  :=  0.38
>=  0.26  <  0.31  :=  0.44
>=  0.31  <  0.36  :=  0.50
>=  0.36  <  0.41  :=  0.54
>=  0.41  <  0.46  :=  0.58
>=  0.46  <  0.51  :=  0.62
>=  0.51  <  0.56  :=  0.66
>=  0.56  <  0.61  :=  0.70
>=  0.61  <  0.66  :=  0.74
>=  0.66  <  0.71  :=  0.78
>=  0.71  <  0.76  :=  0.82
>=  0.76  <  0.81  :=  0.86
>=  0.81  <  0.86  :=  0.90
>=  0.86  <  0.91  :=  0.94
>=  0.91  <  0.96  :=  0.98
>=  0.96  <  1.01  :=  1.00

BASIC

Works with: QBasic

This could also be done by building an array, but I felt that this was simpler.

<lang qbasic>DECLARE FUNCTION PriceFraction! (price AS SINGLE)

RANDOMIZE TIMER DIM x AS SINGLE x = RND PRINT x, PriceFraction(x)

FUNCTION PriceFraction! (price AS SINGLE)

   'returns price unchanged if invalid value
   SELECT CASE price
       CASE IS < 0!
           PriceFraction! = price
       CASE IS < .06
           PriceFraction! = .1
       CASE IS < .11
           PriceFraction! = .18
       CASE IS < .16
           PriceFraction! = .26
       CASE IS < .21
           PriceFraction! = .32
       CASE IS < .26
           PriceFraction! = .38
       CASE IS < .31
           PriceFraction! = .44
       CASE IS < .36
           PriceFraction! = .5
       CASE IS < .41
           PriceFraction! = .54
       CASE IS < .46
           PriceFraction! = .58
       CASE IS < .51
           PriceFraction! = .62
       CASE IS < .56
           PriceFraction! = .66
       CASE IS < .61
           PriceFraction! = .7
       CASE IS < .66
           PriceFraction! = .74
       CASE IS < .71
           PriceFraction! = .78
       CASE IS < .76
           PriceFraction! = .82
       CASE IS < .81
           PriceFraction! = .86
       CASE IS < .86
           PriceFraction! = .9
       CASE IS < .91
           PriceFraction! = .94
       CASE IS < .96
           PriceFraction! = .98
       CASE IS < 1.01
           PriceFraction! = 1!
       CASE ELSE
           PriceFraction! = price
   END SELECT

END FUNCTION</lang>

Sample outputs (run 5 times):

.7388727      .82
.8593103      .9
.826687       .9
.3444635      .5
.0491907      .1

Clipper

<lang dbase>FUNCTION PriceFraction( npQuantDispensed )

   LOCAL aPriceFraction := { {0,.06,.1},;
                           {.06,.11,.18}, ;
                           {.11,.16,.26}, ;
                           {.16,.21,.32}, ;
                           {.21,.26,.38}, ;
                           {.26,.31,.44}, ;
                           {.31,.36,.5}, ;
                           {.36,.41,.54}, ;
                           {.41,.46,.58}, ;
                           {.46,.51,.62}, ;
                           {.51,.56,.66}, ;
                           {.56,.61,.7}, ;
                           {.61,.66,.74}, ;
                           {.66,.71,.78}, ;
                           {.71,.76,.82}, ;
                           {.76,.81,.86}, ;
                           {.81,.86,.9}, ;
                           {.86,.91,.94}, ;
                           {.91,.96,.98} }
   LOCAL nResult
   LOCAL nScan
   IF npQuantDispensed = 0
           nResult = 0
   ELSEIF npQuantDispensed >= .96
           nResult = 1
   ELSE
           nScan := ASCAN( aPriceFraction, ;
                  { |aItem| npQuantDispensed >= aItem[ 1 ] .AND.;
                            npQuantDispensed <  aItem[ 2 ] } )
           nResult := aPriceFraction[ nScan ][ 3 ]
   END IF
   RETURN nResult</lang>

J

Solution: <lang j>le =: -0.96 0.91 0.86 0.81 0.76 0.71 0.66 0.61 0.56 0.51 0.46 0.41 0.36 0.31 0.26 0.21 0.16 0.11 0.06 0.0 out =: 1.00 0.98 0.94 0.90 0.86 0.82 0.78 0.74 0.70 0.66 0.62 0.58 0.54 0.50 0.44 0.38 0.32 0.26 0.18 0.1

priceFraction =: out {~ le I. -</lang>

Usage: <lang j> priceFraction 0.34 0.070145 0.06 0.05 0.50214 0.56 1 0.99 0 0.5 0.18 0.18 0.1 0.62 0.7 1 1 0.1</lang>

Oz

Using a for-loop with return and a default value for values >= 1.01. For out-of-range input, a "failed value" is returned, i.e. a value that throws an exception when it is accessed.

<lang oz>fun {PriceFraction X}

  OutOfRange = {Value.failed outOfRange(X)}

in

  for Limit#Result in
     [0.00#OutOfRange
      0.06#0.10 0.11#0.18 0.16#0.26 0.21#0.32 0.26#0.38 0.31#0.44 0.36#0.5
      0.41#0.54 0.46#0.58 0.51#0.62 0.56#0.66 0.61#0.70 0.66#0.74 0.71#0.78
      0.76#0.82 0.81#0.86 0.86#0.90 0.91#0.94 0.96#0.98 1.01#1.00
     ]
     return:Return
     default:OutOfRange
  do
     if X < Limit then {Return Result} end 
  end

end</lang>

PureBasic

<lang PureBasic>Procedure.f PriceFraction(price.f)

 ;returns price unchanged if value is invalid 
 Protected fraction
 Select price * 100
   Case 0 To 5
     fraction = 10
   Case 06 To 10
     fraction = 18
   Case 11 To 15
     fraction = 26
   Case 16 To 20
     fraction = 32
   Case 21 To 25
     fraction = 38
   Case 26 To 30
     fraction = 44
   Case 31 To 35
     fraction = 5
   Case 36 To 40
     fraction = 54
   Case 41 To 45
     fraction = 58
   Case 46 To 50
     fraction = 62
   Case 51 To 55
     fraction = 66
   Case 56 To 60
     fraction = 7
   Case 61 To 65
     fraction = 74
   Case 66 To 70
     fraction = 78
   Case 71 To 75
     fraction = 82
   Case 76 To 80
     fraction = 86
   Case 81 To 85
     fraction = 9
   Case 86 To 90
     fraction = 94
   Case 91 To 95
     fraction = 98
   Case 96 To 100
     fraction = 100
   Default
     ProcedureReturn price
 EndSelect
 
 ProcedureReturn fraction / 100

EndProcedure

If OpenConsole()

 Define x.f, i
 
 For i = 1 To 10
   x = Random(10000)/10000
   PrintN(StrF(x, 4) + " -> " + StrF(PriceFraction(x), 2))
 Next
 
 Print(#CRLF$ + #CRLF$ + "Press ENTER to exit")
 Input()
 CloseConsole()

EndIf</lang> Sample output:

0.3793 -> 0.54
0.4425 -> 0.58
0.0746 -> 0.18
0.6918 -> 0.78
0.2993 -> 0.44
0.5486 -> 0.66
0.7848 -> 0.86
0.9383 -> 0.98
0.2292 -> 0.38
0.9560 -> 1.00

Python

Using the bisect standard module to reduce the comparisons with members of the cin array.

<lang python>>>> import bisect >>> def pricerounder(pricein): cin = [0.06, 0.11, 0.16, 0.21, 0.26, 0.31, 0.36, 0.41, 0.46, 0.51, 0.56, 0.61, 0.66, 0.71, 0.76, 0.81, 0.86, 0.91, 0.96, 1.01] cout = [0.10, 0.18, 0.26, 0.32, 0.38, 0.44, 0.50, 0.54, 0.58, 0.62, 0.66, 0.70, 0.74, 0.78, 0.82, 0.86, 0.90, 0.94, 0.98, 1.00] return cout[ bisect.bisect_right(cin, pricein) ]</lang>

When dealing with money it is good to think about possible loss of precision. If we change the units to be integer cents we could use the following exact routine: <lang python>>>> import bisect >>> _cin = [ 6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61, 66, 71, 76, 81, 86, 91, 96, 101] >>> _cout = [10, 18, 26, 32, 38, 44, 50, 54, 57, 62, 66, 70, 74, 78, 82, 86, 90, 94, 98, 100] >>> def centsrounder(centsin): return _cout[ bisect.bisect_right(_cin, centsin) ]</lang> Other options are to use the fractions or decimals modules for calculating money to a known precision.

Tcl

Structured as two functions, one to parse the input data as described in the problem into a form which Tcl can work with easily, and the other to perform the mapping. <lang tcl># Used once to turn the table into a "nice" form proc parseTable table {

   set map {}
   set LINE_RE {^ *>= *([0-9.]+) *< *([0-9.]+) *:= *([0-9.]+) *$}
   foreach line [split $table \n] {

if {[string trim $line] eq ""} continue if {[regexp $LINE_RE $line -> min max target]} { lappend map $min $max $target } else { error "invalid table format: $line" }

   }
   return $map

}

  1. How to apply the "nice" table to a particular value

proc priceFraction {map value} {

   foreach {minimum maximum target} $map {

if {$value >= $minimum && $value < $maximum} {return $target}

   }
   # Failed to map; return the input
   return $value

}</lang> How it is used: <lang tcl># Make the mapping set inputTable {

   >=  0.00  <  0.06  :=  0.10
   >=  0.06  <  0.11  :=  0.18
   >=  0.11  <  0.16  :=  0.26
   >=  0.16  <  0.21  :=  0.32
   >=  0.21  <  0.26  :=  0.38
   >=  0.26  <  0.31  :=  0.44
   >=  0.31  <  0.36  :=  0.50
   >=  0.36  <  0.41  :=  0.54
   >=  0.41  <  0.46  :=  0.58
   >=  0.46  <  0.51  :=  0.62
   >=  0.51  <  0.56  :=  0.66
   >=  0.56  <  0.61  :=  0.70
   >=  0.61  <  0.66  :=  0.74
   >=  0.66  <  0.71  :=  0.78
   >=  0.71  <  0.76  :=  0.82
   >=  0.76  <  0.81  :=  0.86
   >=  0.81  <  0.86  :=  0.90
   >=  0.86  <  0.91  :=  0.94
   >=  0.91  <  0.96  :=  0.98
   >=  0.96  <  1.01  :=  1.00

} set map [parseTable $inputTable]

  1. Apply the mapping to some inputs (from the Oz example)

foreach example {.7388727 .8593103 .826687 .3444635 .0491907} {

   puts "$example -> [priceFraction $map $example]"

}</lang> Output:

.7388727 -> 0.82
.8593103 -> 0.90
.826687 -> 0.90
.3444635 -> 0.50
.0491907 -> 0.10