From 96985cec5fc783e8db6ba24e5fe2d2221328efa1 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sun, 3 Aug 2025 14:10:37 +1000 Subject: [PATCH] make.tcl for unix-like platforms ensure copy of runtime is writable; add dollarcent module --- src/bootsupport/modules/README.md | 48 +- .../punk/mix/commandset/project-0.1.0.tm | 15 +- src/bootsupport/modules/test/tomlish-1.1.1.tm | Bin 35259 -> 0 bytes src/bootsupport/modules/test/tomlish-1.1.3.tm | Bin 47064 -> 0 bytes src/make.tcl | 3 + .../mix/commandset/project-999999.0a1.0.tm | 15 +- .../custom/_project/punk.basic/src/make.tcl | 4 + .../punk/mix/commandset/project-0.1.0.tm | 15 +- .../_project/punk.project-0.1/src/make.tcl | 4 + .../punk/mix/commandset/project-0.1.0.tm | 15 +- .../_project/punk.shell-0.1/src/make.tcl | 4 + .../src/bootsupport/modules/README.md | 48 +- src/vendormodules/dollarcent-1.1.tm | 1522 +++++++++++++++++ src/vendormodules/include_modules.config | 1 + .../_vfscommon.vfs/modules/dollarcent-1.1.tm | 1522 +++++++++++++++++ .../punk/mix/commandset/project-0.1.0.tm | 15 +- 16 files changed, 3158 insertions(+), 73 deletions(-) delete mode 100644 src/bootsupport/modules/test/tomlish-1.1.1.tm delete mode 100644 src/bootsupport/modules/test/tomlish-1.1.3.tm create mode 100644 src/vendormodules/dollarcent-1.1.tm create mode 100644 src/vfs/_vfscommon.vfs/modules/dollarcent-1.1.tm diff --git a/src/bootsupport/modules/README.md b/src/bootsupport/modules/README.md index ed6e9672..127aed2b 100644 --- a/src/bootsupport/modules/README.md +++ b/src/bootsupport/modules/README.md @@ -1,24 +1,24 @@ -This is primarily for tcl .tm modules required for your bootstrapping/make/build process. -It could include other files necessary for this process. - -The .tm modules here may be required for your build script if it intended the installation operator uses an existing tclsh or other shell as opposed to a tclkit you may have for distribution which is more likely to include necessary libraries. - -The modules here are loaded by your initialisation scripts and so can be a snapshot of different versions than those in your project src. -The modules can be your own, or 3rd party such as individual items from tcllib. - -You can copy modules from a running punk shell to this location using the dev command. - -e.g -dev lib.copyasmodule some::module::lib bootsupport - -The dev command will help you pick the latest version, and will create any necessary file structure matching the namespace of the package. - -e.g the result might be a file such as -/src/bootsupport/some/module/lib-0.1.tm - -The originating library may not yet be in .tm form. -You can copy a pkgIndex.tcl based library that is composed of a single .tcl file the same way using the above process and it will automatically name and file it appropriately but you need to check that the library doesn't require/load additional files - and that it is Tcl script only. - -Always verify that the library is copyable in this manner and test in a shell with tcl::tm::path pointed to ./bootsupport that it works. - - +This is primarily for tcl .tm modules required for your bootstrapping/make/build process. +It could include other files necessary for this process. + +The .tm modules here may be required for your build script if it intended the installation operator uses an existing tclsh or other shell as opposed to a tclkit you may have for distribution which is more likely to include necessary libraries. + +The modules here are loaded by your initialisation scripts and so can be a snapshot of different versions than those in your project src. +The modules can be your own, or 3rd party such as individual items from tcllib. + +You can copy modules from a running punk shell to this location using the dev command. + +e.g +dev lib.copyasmodule some::module::lib bootsupport + +The dev command will help you pick the latest version, and will create any necessary file structure matching the namespace of the package. + +e.g the result might be a file such as +/src/bootsupport/some/module/lib-0.1.tm + +The originating library may not yet be in .tm form. +You can copy a pkgIndex.tcl based library that is composed of a single .tcl file the same way using the above process and it will automatically name and file it appropriately but you need to check that the library doesn't require/load additional files - and that it is Tcl script only. + +Always verify that the library is copyable in this manner and test in a shell with tcl::tm::path pointed to ./bootsupport that it works. + + diff --git a/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm b/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm index 4f108187..8384197a 100644 --- a/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm +++ b/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm @@ -433,20 +433,26 @@ namespace eval punk::mix::commandset::project { #scan all files in template # #TODO - deck command to substitute templates? - set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] + set templateinfo_list [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set stripprefix [file normalize $layout_path] set tagmap [list [lib::template_tag project] $projectname] - if {[llength $templatefiles]} { + if {[llength $templateinfo_list]} { puts stdout "Filling template file placeholders with the following tag map:" foreach {placeholder value} $tagmap { puts stdout " $placeholder -> $value" } } - foreach templatefullpath $templatefiles { + foreach templateinfo $templateinfo_list { + lassign $templateinfo templatefullpath template_tagnames_found set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] - set fpath [file join $projectdir $templatetail] + foreach t $template_tagnames_found { + if {"%$t%" ni [dict keys $tagmap]} { + puts stderr "warning: No substitution available for tag: %$t% in $fpath" + } + } + if {[file exists $fpath]} { set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd set data2 [string map $tagmap $data] @@ -458,7 +464,6 @@ namespace eval punk::mix::commandset::project { puts stderr "warning: Missing template file $fpath" } } - #todo - tag substitutions in src/doc tree ::cd $projectdir diff --git a/src/bootsupport/modules/test/tomlish-1.1.1.tm b/src/bootsupport/modules/test/tomlish-1.1.1.tm deleted file mode 100644 index d365bab1e00edd610a3a4ae19fa00fb3d3e10a44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35259 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 =~%S9X~P<8_qAgpcJyVkE~kMrOq8!Ga`H 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 TJTCwME$~|s+@ZORpZ)EBiE0Aw diff --git a/src/bootsupport/modules/test/tomlish-1.1.3.tm b/src/bootsupport/modules/test/tomlish-1.1.3.tm deleted file mode 100644 index 8afb43d956b0212bfd728b88613392b2099993ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47064 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 E04sm47XSbN diff --git a/src/make.tcl b/src/make.tcl index 2deda8a3..8cb8153c 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -3006,6 +3006,8 @@ foreach vfstail $vfs_tails { #'archive' based zip offsets - editable in 7z,peazip file copy $raw_runtime $buildfolder/$vfsname.new + #runtime in runtime folder may not have write perm set - ensure the copy does as we need to append + catch {exec chmod +w $buildfolder/$vfsname.new} file delete $buildfolder/$vfsname.zip if {[info commands ::tcl::zipfs] ne ""} { @@ -3073,6 +3075,7 @@ foreach vfstail $vfs_tails { #copy the version that is mounted in this runtime to vfsname.new if {[catch { file copy -force $building_runtime $buildfolder/$vfsname.new + catch {exec chmod +x $buildfolder/$vfsname.new} } errM]} { puts stderr "$kit_type 'file copy -force $building_runtime $buildfolder/$vfsname.new' failed\n$errM" error $errM diff --git a/src/modules/punk/mix/commandset/project-999999.0a1.0.tm b/src/modules/punk/mix/commandset/project-999999.0a1.0.tm index 4b2ae5cf..aecbc39c 100644 --- a/src/modules/punk/mix/commandset/project-999999.0a1.0.tm +++ b/src/modules/punk/mix/commandset/project-999999.0a1.0.tm @@ -433,20 +433,26 @@ namespace eval punk::mix::commandset::project { #scan all files in template # #TODO - deck command to substitute templates? - set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] + set templateinfo_list [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set stripprefix [file normalize $layout_path] set tagmap [list [lib::template_tag project] $projectname] - if {[llength $templatefiles]} { + if {[llength $templateinfo_list]} { puts stdout "Filling template file placeholders with the following tag map:" foreach {placeholder value} $tagmap { puts stdout " $placeholder -> $value" } } - foreach templatefullpath $templatefiles { + foreach templateinfo $templateinfo_list { + lassign $templateinfo templatefullpath template_tagnames_found set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] - set fpath [file join $projectdir $templatetail] + foreach t $template_tagnames_found { + if {"%$t%" ni [dict keys $tagmap]} { + puts stderr "warning: No substitution available for tag: %$t% in $fpath" + } + } + if {[file exists $fpath]} { set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd set data2 [string map $tagmap $data] @@ -458,7 +464,6 @@ namespace eval punk::mix::commandset::project { puts stderr "warning: Missing template file $fpath" } } - #todo - tag substitutions in src/doc tree ::cd $projectdir diff --git a/src/project_layouts/custom/_project/punk.basic/src/make.tcl b/src/project_layouts/custom/_project/punk.basic/src/make.tcl index fd30f208..8cb8153c 100644 --- a/src/project_layouts/custom/_project/punk.basic/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.basic/src/make.tcl @@ -375,6 +375,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set support_contents_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] { #set contents [glob -nocomplain -dir $p -tail *] + if {![file exists $p]} {continue} set contents [punkboot::lib::folder_nondotted_children $p] set readmeposn [lsearch -nocase $contents readme.md] #don't assume 'ledit' available @@ -3005,6 +3006,8 @@ foreach vfstail $vfs_tails { #'archive' based zip offsets - editable in 7z,peazip file copy $raw_runtime $buildfolder/$vfsname.new + #runtime in runtime folder may not have write perm set - ensure the copy does as we need to append + catch {exec chmod +w $buildfolder/$vfsname.new} file delete $buildfolder/$vfsname.zip if {[info commands ::tcl::zipfs] ne ""} { @@ -3072,6 +3075,7 @@ foreach vfstail $vfs_tails { #copy the version that is mounted in this runtime to vfsname.new if {[catch { file copy -force $building_runtime $buildfolder/$vfsname.new + catch {exec chmod +x $buildfolder/$vfsname.new} } errM]} { puts stderr "$kit_type 'file copy -force $building_runtime $buildfolder/$vfsname.new' failed\n$errM" error $errM diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm index 4f108187..8384197a 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm @@ -433,20 +433,26 @@ namespace eval punk::mix::commandset::project { #scan all files in template # #TODO - deck command to substitute templates? - set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] + set templateinfo_list [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set stripprefix [file normalize $layout_path] set tagmap [list [lib::template_tag project] $projectname] - if {[llength $templatefiles]} { + if {[llength $templateinfo_list]} { puts stdout "Filling template file placeholders with the following tag map:" foreach {placeholder value} $tagmap { puts stdout " $placeholder -> $value" } } - foreach templatefullpath $templatefiles { + foreach templateinfo $templateinfo_list { + lassign $templateinfo templatefullpath template_tagnames_found set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] - set fpath [file join $projectdir $templatetail] + foreach t $template_tagnames_found { + if {"%$t%" ni [dict keys $tagmap]} { + puts stderr "warning: No substitution available for tag: %$t% in $fpath" + } + } + if {[file exists $fpath]} { set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd set data2 [string map $tagmap $data] @@ -458,7 +464,6 @@ namespace eval punk::mix::commandset::project { puts stderr "warning: Missing template file $fpath" } } - #todo - tag substitutions in src/doc tree ::cd $projectdir diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl index fd30f208..8cb8153c 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl @@ -375,6 +375,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set support_contents_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] { #set contents [glob -nocomplain -dir $p -tail *] + if {![file exists $p]} {continue} set contents [punkboot::lib::folder_nondotted_children $p] set readmeposn [lsearch -nocase $contents readme.md] #don't assume 'ledit' available @@ -3005,6 +3006,8 @@ foreach vfstail $vfs_tails { #'archive' based zip offsets - editable in 7z,peazip file copy $raw_runtime $buildfolder/$vfsname.new + #runtime in runtime folder may not have write perm set - ensure the copy does as we need to append + catch {exec chmod +w $buildfolder/$vfsname.new} file delete $buildfolder/$vfsname.zip if {[info commands ::tcl::zipfs] ne ""} { @@ -3072,6 +3075,7 @@ foreach vfstail $vfs_tails { #copy the version that is mounted in this runtime to vfsname.new if {[catch { file copy -force $building_runtime $buildfolder/$vfsname.new + catch {exec chmod +x $buildfolder/$vfsname.new} } errM]} { puts stderr "$kit_type 'file copy -force $building_runtime $buildfolder/$vfsname.new' failed\n$errM" error $errM diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm index 4f108187..8384197a 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm @@ -433,20 +433,26 @@ namespace eval punk::mix::commandset::project { #scan all files in template # #TODO - deck command to substitute templates? - set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] + set templateinfo_list [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set stripprefix [file normalize $layout_path] set tagmap [list [lib::template_tag project] $projectname] - if {[llength $templatefiles]} { + if {[llength $templateinfo_list]} { puts stdout "Filling template file placeholders with the following tag map:" foreach {placeholder value} $tagmap { puts stdout " $placeholder -> $value" } } - foreach templatefullpath $templatefiles { + foreach templateinfo $templateinfo_list { + lassign $templateinfo templatefullpath template_tagnames_found set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] - set fpath [file join $projectdir $templatetail] + foreach t $template_tagnames_found { + if {"%$t%" ni [dict keys $tagmap]} { + puts stderr "warning: No substitution available for tag: %$t% in $fpath" + } + } + if {[file exists $fpath]} { set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd set data2 [string map $tagmap $data] @@ -458,7 +464,6 @@ namespace eval punk::mix::commandset::project { puts stderr "warning: Missing template file $fpath" } } - #todo - tag substitutions in src/doc tree ::cd $projectdir diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl index fd30f208..8cb8153c 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl @@ -375,6 +375,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set support_contents_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths {*}$sourcesupport_module_paths {*}$sourcesupport_library_paths] { #set contents [glob -nocomplain -dir $p -tail *] + if {![file exists $p]} {continue} set contents [punkboot::lib::folder_nondotted_children $p] set readmeposn [lsearch -nocase $contents readme.md] #don't assume 'ledit' available @@ -3005,6 +3006,8 @@ foreach vfstail $vfs_tails { #'archive' based zip offsets - editable in 7z,peazip file copy $raw_runtime $buildfolder/$vfsname.new + #runtime in runtime folder may not have write perm set - ensure the copy does as we need to append + catch {exec chmod +w $buildfolder/$vfsname.new} file delete $buildfolder/$vfsname.zip if {[info commands ::tcl::zipfs] ne ""} { @@ -3072,6 +3075,7 @@ foreach vfstail $vfs_tails { #copy the version that is mounted in this runtime to vfsname.new if {[catch { file copy -force $building_runtime $buildfolder/$vfsname.new + catch {exec chmod +x $buildfolder/$vfsname.new} } errM]} { puts stderr "$kit_type 'file copy -force $building_runtime $buildfolder/$vfsname.new' failed\n$errM" error $errM diff --git a/src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/README.md b/src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/README.md index ed6e9672..127aed2b 100644 --- a/src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/README.md +++ b/src/project_layouts/vendor/punk/project-0.1/src/bootsupport/modules/README.md @@ -1,24 +1,24 @@ -This is primarily for tcl .tm modules required for your bootstrapping/make/build process. -It could include other files necessary for this process. - -The .tm modules here may be required for your build script if it intended the installation operator uses an existing tclsh or other shell as opposed to a tclkit you may have for distribution which is more likely to include necessary libraries. - -The modules here are loaded by your initialisation scripts and so can be a snapshot of different versions than those in your project src. -The modules can be your own, or 3rd party such as individual items from tcllib. - -You can copy modules from a running punk shell to this location using the dev command. - -e.g -dev lib.copyasmodule some::module::lib bootsupport - -The dev command will help you pick the latest version, and will create any necessary file structure matching the namespace of the package. - -e.g the result might be a file such as -/src/bootsupport/some/module/lib-0.1.tm - -The originating library may not yet be in .tm form. -You can copy a pkgIndex.tcl based library that is composed of a single .tcl file the same way using the above process and it will automatically name and file it appropriately but you need to check that the library doesn't require/load additional files - and that it is Tcl script only. - -Always verify that the library is copyable in this manner and test in a shell with tcl::tm::path pointed to ./bootsupport that it works. - - +This is primarily for tcl .tm modules required for your bootstrapping/make/build process. +It could include other files necessary for this process. + +The .tm modules here may be required for your build script if it intended the installation operator uses an existing tclsh or other shell as opposed to a tclkit you may have for distribution which is more likely to include necessary libraries. + +The modules here are loaded by your initialisation scripts and so can be a snapshot of different versions than those in your project src. +The modules can be your own, or 3rd party such as individual items from tcllib. + +You can copy modules from a running punk shell to this location using the dev command. + +e.g +dev lib.copyasmodule some::module::lib bootsupport + +The dev command will help you pick the latest version, and will create any necessary file structure matching the namespace of the package. + +e.g the result might be a file such as +/src/bootsupport/some/module/lib-0.1.tm + +The originating library may not yet be in .tm form. +You can copy a pkgIndex.tcl based library that is composed of a single .tcl file the same way using the above process and it will automatically name and file it appropriately but you need to check that the library doesn't require/load additional files - and that it is Tcl script only. + +Always verify that the library is copyable in this manner and test in a shell with tcl::tm::path pointed to ./bootsupport that it works. + + diff --git a/src/vendormodules/dollarcent-1.1.tm b/src/vendormodules/dollarcent-1.1.tm new file mode 100644 index 00000000..4c69c854 --- /dev/null +++ b/src/vendormodules/dollarcent-1.1.tm @@ -0,0 +1,1522 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2025 +# +# @@ Meta Begin +# Application dollarcent 1.1 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# doctools header +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[manpage_begin dollarcent_module_dollarcent 0 1.1] +#[copyright "2025"] +#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] +#[moddesc {-}] [comment {-- Description at end of page heading --}] +#[require dollarcent] +#[keywords module] +#[description] +#[para] - + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section Overview] +#[para] overview of dollarcent +#[subsection Concepts] +#[para] - + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[subsection dependencies] +#[para] packages used by dollarcent +#[list_begin itemized] + +package require Tcl 8.6- +#*** !doctools +#[item] [package {Tcl 8.6-}] + + +#*** !doctools +#[list_end] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section API] + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Base namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +namespace eval dollarcent { + #only export main api - see at end of namespace + #todo - tidy up functions in main namespace and move some to lib etc + #namespace export {[a-z]*}; # Convention: export all lowercase + #variable xyz + + #*** !doctools + #[subsection {Namespace dollarcent}] + #[para] Core API functions for dollarcent + #[list_begin definitions] + + variable strict 1 ;#0|1|2 + # 0 = ignore intermixing of conversions with different fractional-cent units. + # 1 = warn with msg to stderr about intermixing + # 2 = raise error + + #2025 - todo + #WARNING - statefulness of variables such as strict,units,rounding_method - suggest we should use an object-like interface - otherwise concurrent use in different contexts is risky. + + variable units "" ;#last active units used for fractional-cents. Raise error or warning if attempt to use a different conversion unit without explicitly resetting. + + proc units {{fractional_cent_unit "-"}} { + variable units + if {$fractional_cent_unit eq "-"} { + return $units + } else { + if {$fractional_cent_unit eq ""} { + #allow 'reset' so that whatever next unit is used in conversion will become next units value. + set units "" + } else { + foreach {a b} {c c-0 xc c-1 cc c-2 mc c-3 _xc c-4 _cc c-5 _mc c-6} { + if {$fractional_cent_unit eq $a} { + set fractional_cent_unit $b + break + } + } + if {![string match "c-*" $fractional_cent_unit]} { + error "Expected fractional cent unit such as 'c' 'xc' 'mc' '_xc' or 'c-1' 'c-2' 'c-3' 'c-4' etc - got: '$fractional_cent_unit'" + } + set units $fractional_cent_unit + } + } + } + + variable rounding_methods + + set rounding_methods [dict create] + dict set rounding_methods halfeven {unbiased statisticians bankers convergent dutch gaussian} + dict set rounding_methods halfup {up euro} + dict set rounding_methods halfdown {down} + dict set rounding_methods halfawayfromzero {} + dict set rounding_methods halftowardszero {} + dict set rounding_methods unsupported {stochastic swedish cash} + #'cash' method (sometimes called swedish) depends on smallest unit of currency in play for a particular jurisdication/country + + variable rounding_method halfup ;#default rounding 0.5 upwards + + proc roundingmethods {} { + variable rounding_methods + return $rounding_methods + } + + #--------------------------------- + #rounding implementations + + proc do_round_halfeven {num e1 e2} { + if {($e1 eq "5") && ($e2 eq "0")} { + set last [string range $num end end] + if {($last % 2) == 0} { + #even + incr num + } + } elseif {$e1 eq "5"} { + incr num + } + return $num + } + + proc do_round_halfup {num e1 e2} { + if {$e1 eq "5"} { + incr num + } + return $num + } + proc do_round_halfdown {num e1 e2} { + if {$e1 eq "5"} { + if {$e2 ne "0"} { + # ${num}5x is > ${num}50 round up. + incr num + } + #exactly ${num}50 - leave num as is. (ie round down) + } + return $num + } + proc do_round_halfawayfromzero {num e1 e2} { + if {$num > 0} { + set num [do_round_halfup $num $e1 $e2] + } else { + if {$e1 eq "5"} { + if {$e2 ne "0"} { + incr num -1 + } + } + } + return $num + } + proc do_round_halftowardszero {num e1 e2} { + if {$num > 0} { + if {$e1 eq "5"} { + if {$e2 ne "0"} { + incr num -1 + } + } + } else { + set num [do_round_halfup $num $e1 $e2] + } + return $num + } + #---------------------------------- + proc do_round {num e1 e2} { + variable rounding_method + variable rounding_methods + set meth "-" + if {$rounding_method ni [dict keys $rounding_methods]} { + foreach m [dict keys $rounding_methods] { + set aliases [dict get $rounding_methods $m] + if {$rounding_method in $aliases} { + if {"::dollarcent::do_round_$m" in [info commands ::dollarcent::*]} { + set meth $m + } + break + } + } + } else { + set meth $rounding_method + } + if {$meth eq "-"} { + error "rounding method '$rounding_method' not supported" + } + + do_round_$meth $num $e1 $e2 + } + + #2010-04 - roundingmethod not yet in use. review. Get the basic roundhalfup version working and fully tested first! + proc roundingmethod {args} { + variable rounding_method + variable rounding_methods + if {![llength $args]} { + return $rounding_method + } else { + set allmethods [list] + foreach m [dict keys $rounding_methods] { + if {$m ne "unsupported"} { + lappend allmethods $m + lappend allmethods {*}[dict get $rounding_methods $m] + } + } + + set desiredmethod [lindex $args 0] + if {$desiredmethod ni $allmethods} { + error "rounding method '$desiredmethod' is not supported" + } + set rounding_method $desiredmethod + } + } + + + set ns [namespace current] + + + #proc convert {amount source TO target args} {} ;#old v 1.0 signature - not easy to alias or pipeline + package require punk::args + punk::args::define { + @id -id ::dollarcent::convert + @cmd -name dollarcent::convert -help\ + "Convert between a floating point number representing a value specified in the + major unit of a currency - to an integer representing the number of fractional + parts of the minor unit. + e.g if dealing with dollars and cents and using c-level cc (c-2) + convert between a dollar figure like 1.00 + and a quantity of fractional-cents 10000" + @form -form dollar + @leaders -min 3 -max 3 + double_amount_type -type string -choices {dollars} -help\ + "'dollars' means convert FROM the large unit of whatever currency is + being processed" + to -type literal(TO)|literal(to) + target_type -type string -choicerestricted 0 -choices {c xc cc mc _xc _cc _mc}\ + -choicelabels { + "c"\ + "equiv: c-0" + "xc"\ + "equiv: c-1" + "cc"\ + "equiv: c-2" + "mc"\ + "equiv: c-3" + "_xc"\ + "equiv: c-4" + "_cc"\ + "equiv: c-5" + "_mc"\ + "equiv: c-6" + } -help\ + "The scale of the small unit of currency for calculation purposes. + e.g c (c-0) is 1 major unit to 100 + mc (c-3) is 1 major unit to 100_000 + _mc (c-6) is 1 major unit to 100_000_000 + + c happens to correspond to USD $1 to 100c + and _mc happens to correspond to 1BTC to 100Million Satoshi, + but generally you'd want a higher level than the number of + small units per major unit so that rounding errors are reduced + to well below the level of the minor currency unit. + " + @opts + -strict -type integer -default 1 -choices {0 1 2}\ + -choicelabels { + 0 {Silence even if units (c-level) used don't match last used} + 1 {Warn on stderr if units have changed} + 2 {Error if units have changed} + } -choicecolumns 1 + @values -min 1 -max 1 + amount -type double -help\ + "Quantity of the largest units of the currency (a conceptual 'Dollar' amount). + For another currency this might be Euros or BTC. + It is specified as a floating-point number with at least 2 decimal places, + but the maximum allowed decimal places is determined by the target_type." + + + @form -form cent + @leaders -min 3 -max 3 + integer_amount_type -type string -choicerestricted 0 -choices {c xc cc mc _xc _cc _mc}\ + -choicelabels { + "c"\ + "equiv: c-0" + "xc"\ + "equiv: c-1" + "cc"\ + "equiv: c-2" + "mc"\ + "equiv: c-3" + "_xc"\ + "equiv: c-4" + "_cc"\ + "equiv: c-5" + "_mc"\ + "equiv: c-6" + } -help\ + "The scale of the small unit of currency for calculation purposes. + e.g c (c-0) is 1 major unit to 100 + mc (c-3) is 1 major unit to 100_000 + _mc (c-6) is 1 major unit to 100_000_000 + + c happens to correspond to USD $1 to 100c + and _mc happens to correspond to 1BTC to 100Million Satoshi, + but generally you'd want a higher level than the number of + small units per major unit so that rounding errors are reduced + to well below the level of the minor currency unit. + " + to -type literal(TO)|literal(to) + target_type -type string -choices {dollars} -help\ + "'dollars' means convert TO the large unit of whatever currency is + being processed" + + @opts + -strict -type integer -default 1 -choices {0 1 2}\ + -choicelabels { + 0 {Silence even if units (c-level) used don't match last used} + 1 {Warn on stderr if units have changed} + 2 {Error if units have changed} + } -choicecolumns 1 + @values -min 1 -max 1 + amount -type double -help\ + "Integer quantity being used for conversion to the major unit amount. + The same scale (c-level) must be used in converting to and from large + and small units for any calculations to make sense." + + + } + proc convert {args} { + set amount_type [tcl::prefix::match -error "" {dollars} [lindex $args 0]] + if {$amount_type eq "dollars"} { + set argd [punk::args::parse $args -form dollar withid ::dollarcent::convert] + } else { + set amount_type [lindex $args 0] + set argd [punk::args::parse $args -form cent withid ::dollarcent::convert] + } + + variable strict ;#whether and how to alert programmer if subsequent calls use different fractional-cent units. + variable units ;#we remember the fractional cent units used in previous calls and disallow different units unless explicitly set with dollarcent::units. + + lassign [dict values $argd] leaders opts values received + + set bestrict [dict get $opts -strict] + + set target_type [dict get $leaders target_type] + set amount [dict get $values amount] + + #c-level aliases + set c_aliases [dict create c c-0 xc c-1 cc c-2 mc c-3 _xc c-4 _cc c-5 _mc c-6] + + #puts stdout "->convert $amount $amount_type $TO $target" + if {$amount_type eq {dollars}} { + # where c-0 = cents, c-1 represents cents * 10-1 or 'tenths of a cent', c-2 = 'hundredths of a cent' etc. + #c, xc, cc, mc are shortcuts for these - designed to match the convenience functions dc2c dc2xc dc2cc dc2mc dc2_xc, c2dc xc2dc mc2dc etc + # ie roman numeralesque + # x = 10 + # c = 100 (leading char only - trailing one means cents!) + # m = 1000 + # _x = 10000 (underscore to represent 'overbar' used in roman numerals to indicate multiplication by 1000) + # _c = 100000 + # _m = 1000000 + + #dc2_xc dc2_mc + if {[dict exists $c_aliases $target_type]} { + set target_type [dict get $c_aliases $target_type] + } + + if {$units eq ""} { + #1st use in this interp - ok - just set the units. + set units $target_type + } else { + if {$target_type ne $units} { + set msg "The fractional-cent unit already in use is '$units' but the current conversion specified: '$target_type'. Call 'dollarcent::units ' if you wish to change the size of the fractional-cent in use." + append msg "\ne.g 'c-0' = cents, 'c-1' or 'xc' = tenths of a cent, 'c-2' or 'cc' = hundredths of a cent, 'c-3' or 'mc' = thousandths, 'c-4' or '_xc = 10-thousandths etc." + if {$bestrict == 1} { + puts stderr $msg + } elseif {$bestrict == 2} { + error $msg + } else { + #ignore - but *don't* reset units - this may have been a 'oneshot' use of a different unit by use of the -strict 0 option to 'convert'. + #We require that the programmer specifically set the units. + } + } + } + + + set dollars $amount + + lassign [split $target_type -] _c power + #if {$power > 20} { + # error "dollarcent package probably can't handle such small fractions of a cent. '10 to the -20' may be the limit. Check code in dollarcent module and adjust this test as necessary." + #} + + #puts stdout "power is $power" + #puts stdout "dollars: $dollars" + set format "%lld.%1d%1d[string repeat %1d $power]%s" ;#note - this is lower case ll not numeral 11! + + set vars [lrepeat [expr {$power + 2}] _c] ;#just a list of placeholder vars for the scan function to + + set partcount [scan $dollars $format bucks {*}$vars _disallowed] + set dpcount [expr {$partcount -1}] ;#subtract the LHS to get the count of number of decimal places. + if {$dpcount < 2 || ($dpcount > (2 + $power))} { + return -code error "Incorrect precision for target type '$target_type'. Dollar amount must be a decimal number with between 2 and [expr {2 + $power}] digits following the decimal point." + } + + #------------------------------- + #Note: We could do this.. but this is a floating point multiplication. + # - for $power = 0, this seems to work up to about $(2**45).34 ie 35trillion,184billion,372million,88thousand,832 dollars + # - if the chosen base unit is 10 thousandths of a cent, then rounding errors will occur at somewhere around 550billion dollars. + # - Presumably rounding errors regarding a cent for such large sums are not an issue for the vast majority of use-cases - but it's the intention that this module + # eventually be accurate and flexible enough to be used in all sorts of situations e.g for some pseudo-currency in a virtual-world/game economy or perhaps even in a hyperinflationary environment. + # (No fitness for use in such situations is implied) + #return [expr {round($dollars*100*(10**$power))}] + #------------------------------- + + + set subcents [decmul [str2dec $dollars] [str2dec [expr {100*(10**$power)}]]] + #set result [expr {round([dec2str $subcents])}] ;#don't do this - round will do funny things for large numbers. + set result [dec2str $subcents] + + lassign [split $result .] whole part + set c1 [string range $part 0 0] + if {$c1 >= 5} { + if {$c1 > 5} { + if {$whole > 0} { + incr whole + } else { + incr whole -1 + } + } else { + #c1 exactly 5 + set tail [string range $part 1 end] + if {([string length $tail] ==0) || ($tail == 0)} { + #entirety of 'part' is equivalent to exactly 0.5 + ##!todo - round based on currently active roundingmethod + if {$whole > 0} { + incr whole + } else { + incr whole -1 + } + } else { + #there is something non-zero folowing c1 + #therefore round up - no matter what the currently active rounding method. + if {$whole > 0} { + incr whole + } else { + incr whole -1 + } + } + } + } + return $whole + + } else { + #we're converting fractional cents to dollars & cents. + + if {[string match "-*" $amount]} { + set amount [string range $amount 1 end] + set sign - + } else { + set sign "" + } + + set int_pennyparts $amount + if {[dict exists $c_aliases $amount_type]} { + set amount_type [dict get $c_aliases $amount_type] + } + + if {$units eq ""} { + #1st use in this interp - ok - just set the units. + set units $amount_type + } else { + if {$amount_type ne $units} { + set msg "The fractional-cent (c-level) unit already in use is '$units' but the current conversion specified: '$amount_type'. Call 'dollarcent::units ' if you wish to change the size of the fractional-cent in use." + append msg "\ne.g 'c-0' = cents, 'c-1' or 'xc' = tenths of a cent, 'c-2' or 'cc' = hundredths of a cent, 'c-3' or 'mc' = thousandths, 'c-4' or '_xc = 10-thousandths etc." + if {$bestrict == 1} { + puts stderr $msg + } elseif {$bestrict == 2} { + error $msg + } else { + #ignore - but *don't* reset units - this may have been a 'oneshot' use of a different unit by use of the -strict 0 option to 'convert'. + #We require that the programmer specifically set the units. + } + } + } + + + + lassign [split $amount_type -] _c power + if {$power > 19} { + error "dollarcent package probably can't handle such small fractions of a cent. '10 to the -19' may be the limit. Check code in dollarcent module and adjust this test as necessary." + } + + + if {$target_type ne {dollars}} { + error "For conversion amount_type '$amount_type' - unknown target type '$target_type'" + } + #format string note - this is lowercase ll not number 11! + if {[scan $int_pennyparts %lld%c int_pennyparts _disallowed] == 1} { + #---------------- + #NOTE! 'format %.2f' does implicit rounding - and also varies in behaviour across platforms. This is not suitable for use here. + #return [format %.2f [expr {$int_pennyparts /(100.0 *(10**$power))}]] ;#NOTE - %.2f rounds in a strange manner - and is inconsistent across tcl platforms! + #----------------- + + #----------------- + #return [expr {$int_pennyparts /(100.0 *(10**$power))}] + #----------------- + + set r [decdiv [str2dec $int_pennyparts] [str2dec [expr {100*(10**$power)}]]] + #puts stdout "r -> $r" + + lassign $r _dec significand exp + + #trailing zeroes not useful here + #(normally trailing zeroes are sigfigs) + set r_significand [string reverse $significand] + + set exp2 $exp + foreach z [split $r_significand ""] { + if {$z eq "0"} { + incr exp2 + } else { + break + } + } + set significand2 [string trimright $significand "0"] + + #return [dec2str [list decimal $significand $exp]] ;#dollar result + set result [dec2str [list decimal $significand2 $exp2]] ;#dollar result + + if {[string first . $result] < 0} { + set result $result.00 + } else { + lassign [split $result .] dollarpart centpart + if {[string length $centpart] == 1} { + set centpart ${centpart}0 + } + if {[string length $dollarpart] > 1} { + set dollarpart [string trimleft $dollarpart 0] + } + set result $dollarpart.$centpart + } + return $sign$result + } else { + return -code error "Only an integer number of units can be converted to dollars and cents. Please round before converting. amount:'$int_pennyparts'" + } + } + } + + proc roundcents {dollars} { + lassign [split $dollars .] bucks cents + if {[string length $cents] < 2} { + return -cdoe error "'roundcents' requires that dollar amount be given with at least 2 decimal places" + } + + set decimal_dollaramount [str2dec $dollars] + set decimal_100 [str2dec 100] + set decimal_cents [decmul $decimal_dollaramount $decimal_100] + + set fpcents [dec2str $decimal_cents] + #puts stdout "fpcents-> $fpcents" + + lassign [split $fpcents .] wholecents frac + set frac1 [string range $frac 0 0] + set fractail [string range $frac 1 end] + if {$frac1 >= 5} { + if {$frac1 == 5} { + if {([string length $fractail] == 0) || ($fractail == 0)} { + #entirety of 'frac' is equivalent to .5 + #use active rounding method to round + #!todo - roundingmethods + if {$wholecents > 0} { + incr wholecents + } else { + incr wholecents -1 + } + } else { + #the x in .5x is non-zero - always round up no matter what rounding method is in effect. + if {$wholecents > 0} { + incr wholecents + } else { + incr wholecents -1 + } + } + } else { + if {$wholecents > 0} { + incr wholecents + } else { + incr wholecents -1 + } + } + } + + #set cents [expr {round($fpcents)}] + + set d [c2d -strict 0 $wholecents] + return $d + } + + #normalize a string representing dollars and cents, from a US,AU,NZ etc perspective + #ie doesn't cater for european conventions of comma as decimal separator. + #This normalization from the perspective of this library: + # conversion from other conventions such as European should be done at input/output of whole process. (using locale etc) + + # e.g '$2.1' -> '2.10' + #Must also cope with underscores and commas in combination with leading zeros + #Must not lose decimal places e.g $10.33335 -> 10.33335 + #But we convert both 10. and 10.0 to 10.00 (not correct from a sigfig perspective - but more useful) + proc normaldollars {dollaramount} { + set amount [string trim $dollaramount] + set amount [string map {{-$} {$-}} $amount] ;# -$2.10 equivalent to $-2.10 + if {[string index $amount 0] eq {$}} { + } + set amount [string trimleft $amount {$}] + if {$amount in {"" "-"}} { + error "'$dollaramount' is not in a format that normaldollars can recognize" + } + + if {[string match "-*" $amount]} { + set amount [string range $amount 1 end] + set sign - + } else { + set sign "" + } + if {[string match ".*" $amount]} { + set amount 0$amount + } + if {[string match "*." $amount]} { + set amount ${amount}00 + } + + if {[string first . $amount] < 0} { + set amount $amount.00 + } else { + lassign [split $amount .] dollarpart centpart + if {[string length $centpart] == 1} { + set centpart ${centpart}0 + } + if {[string length $dollarpart] > 1} { + set dollarpart [string trimleft $dollarpart 0] + } + set amount $dollarpart.$centpart + } + + return $sign$amount + } + + + variable scale 38 ;#fairly arbitrary - but 28 supposedly matches that of Python. 'ISO COBOL 2002 standard' uses 32, Transact SQL has a max of 38. + variable maxmantisse [expr {10**$scale}] + variable bndmantisse [expr {10*$maxmantisse}] + proc set_scale {newscale} { + variable scale + variable maxmantisse + variable bndmantisse + + set scale $newscale + set maxmantisse [expr {10**$scale}] + set bndmantisse [expr {10*$maxmantisse}] + } + + + #---------------------------------------------------------------- + # Rescale -- + # Rescale the number (using proper rounding) + # + # Arguments: + # decimal in the form {decimal significand exponent} + # + # Result: + # Rescaled number (as a list) + # + proc decrescale {decimal {newscale ""}} { + variable maxmantisse + variable bndmantisse + + lassign $decimal _decimal significand exponent + + if { abs($significand) <= $maxmantisse } { + return [list decimal $significand $exponent] + } + + set rest [expr {$significand % 10}] ;#for case where $maxmantisse < abs($significand) <= $bndmantisse + while { abs($significand) > $bndmantisse } { + set rest [expr {$significand % 10}] + set significand [expr {$significand/10}] + incr exponent + } + + + if { $rest > 5 } { + if { $significand > 0 } { + incr significand + } else { + incr significand -1 + } + } elseif { $rest == 5 } { + #halfup rounding + if { $significand > 0 } { + incr significand + } else { + incr significand -1 + } + + #bankers rounding + #if { ($significand/10) % 2 == 1 } { + # incr significand + #} + } + + return [list decimal $significand $exponent] + } + + proc str2dec {string} { + set pos [string first . $string] + if {$pos < 0} { + #significand = mantissa + set significand $string + set significand [string trimleft $string "0"] + if {$significand eq ""} { + set significand 0 + } + set exponent 0 + } else { + set fraction [string range $string $pos+1 end] + set significand [string trimleft [string map {. ""} $string] 0] + if {$significand eq ""} { + set significand 0 + } + set exponent [expr {-[string length $fraction]}] + } + return [list decimal $significand $exponent] + } + + proc dec2str {decimal_as_list} { + #puts stdout "==>dec2str $decimal_as_list" + lassign $decimal_as_list _decimal significand exponent + if {![string length $significand]} { + set significand 0 + } + if {$significand < 0} { + set sign - + } else { + set sign "" + } + set significand [string map {- ""} $significand] + if {$exponent > 0} { + if {$significand == 0} { + set string 0 + } else { + set string $sign$significand[string repeat 0 $exponent] + } + } else { + set digits [string length $significand] + + set exponent [expr {abs($exponent)}] + if {$digits >= $exponent} { + if {$exponent == 0} { + set string $sign$significand + } else { + if {$digits == $exponent} { + #e.g 10 * 10**-2 + set lhs "0" + } else { + set lhs [string range $significand 0 [expr {$digits-$exponent-1}]] + } + set string $sign$lhs.[string range $significand [expr {$digits-$exponent}] end] + } + } else { + set string ${sign}0.[string repeat 0 [expr {$exponent-$digits}]]$significand + } + } + return $string + } + + proc multiply_as_decimal {a b args} { + return [dec2str [decmul [str2dec $a] [str2dec $b] {*}$args]] + } + proc divide_as_decimal {a b args} { + return [dec2str [decdiv [str2dec $a] [str2dec $b] {*}$args]] + } + proc add_as_decimal {a b args} { + return [dec2str [decadd [str2dec $a] [str2dec $b] {*}$args]] + } + proc power_as_decimal {a b args} { + return [dec2str [decpow [str2dec $a] [str2dec $b] {*}$args]] + } + + + #round 'val' to 'dp' decimal places + proc dpround {val dp} { + error "not implemented" + } + #round 'val' to 'sigfigs' significant figures + proc sigfiground {val sigfigs} { + error "not implemented" + } + + #significant figures 'are a rather crude way of tracking precision' + # see http://scienceblogs.com/goodmath/2009/03/basics_significant_figures.php + # - NOTE - to review - are sigfigs really even relevant to currency work? + proc sigfigs {floatstr} { + + #1) all non-zero digits are considered significant + #2) zeros between non-zeros are significant. + #3) leading zeros not significant. + + # - for this function - we will assume trailing zeros in numbers with no decimal point are insignificant. + # There is an ambiguity here because it could be that the zeros are significant but the value just happens to be round.. + # we adopt the convention that a trailing . on the input will indicate that the previous zeros are significant. + # e.g "100" has 1 sigfigs but "100." has 3 sigfigs. + + #rules when performing calculations: + #a) for multiplication and division the final answer should contain only as many sigfigs as the number with the *least* number of sigfigs. + #b) for addition and subtraction - keep the number of sigfigs in the input with the smallest number of *decimal places* + + + set i 0 + set s 0 + set in_sigfigs 0 + set sigs "" ;#significant digits + set dp 0 + set in_dp 0 + if {[string first . $floatstr] >= 0} { + set has_radix 1 ;#radix is a more general term for decimal point. + } else { + set has_radix 0 + } + if {[string match "-*" $floatstr]} { + set floatstr [string map {- ""} $floatstr] + incr i + } + foreach c [split $floatstr ""] { + if {$c eq "0"} { + if {$in_sigfigs} { + append sigs "0" + } + } else { + if {$c ne "."} { + if {!$in_sigfigs} { + set in_sigfigs 1 + set s $i + } + append sigs $c + } else { + set in_dp 1 + } + } + if {$c ne "."} { + if {$in_dp} { + incr dp + } + } + incr i + } + if {!$has_radix} { + #no radix. We will consider trailing zeros to be insignificant. + set sigs [string trimright $sigs "0"] + } + + return [list count [string length $sigs] startindex $s digits $sigs dp $dp] + } + + #decimal sum + proc decadd {a b} { + lassign $a _decimal sa ea ;#significand-a (aka mantissa-a), exponent-a + lassign $b _decimal sb eb + + if {$ea > $eb} { + set sa [expr {$sa * 10 ** ($ea-$eb)}] + set er $eb + } else { + set sb [expr {$sb * 10 ** ($eb-$ea)}] + set er $ea + } + set sr [expr {$sa + $sb}] + return [list decimal $sr $er] + } + + proc decpow2 {a b} { + #a**b + lassign $a _decimal sa ea + lassign $b _decimal sb eb + if {$sb eq 0} { + return [list decimal 1 0] + } else { + if {$sb < 0} { + #a**-n = 1/a**n + error "negative exponent not supported - sorry - try plain expr" + + } else { + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + + #powx = intermediate power x + + #sa **(sb * ten_2_eb) * 10**(ea*sb*(ten_2_eb)) + #sa **(sb * ten_2_eb) * 10**(exp1) + #sa **(sb * ten_2_eb) * pow2 + #pow3 * pow2 + + puts stdout "--->sa **(sb * ten_2_eb) * 10**(ea*sb*(ten_2_eb))<---" + puts stdout "--->sa **(sb * ten_2_eb) * 10**(exp1)<---" + + puts stdout "===>[subst {($sa ** ($sb * 10 **$eb)) * (10**($ea*$sb*(10**$eb))) }]<====" + set ten_2_eb [expr {10**$eb}] + puts stdout "->ten_2_eb: $ten_2_eb" + set exp1 [expr {$ea * $sb * $ten_2_eb}] + if {$exp1 < 0 } { + set exp1plus [string map {- ""} $exp1] + set d [expr {10**$exp1plus}] + puts stdout "...10**$exp1plus= $d" + set pow2 [divide_as_decimal 1 $d] + } else { + set pow2 [expr {10**$exp1}] + } + + puts stdout "->exp1: $exp1" + puts stdout "->pow2: $pow2" + + set exp2 [expr {$sb * $ten_2_eb}] + if {$exp2 < 0} { + set exp2plus [string map {- ""} $exp2] + set d [expr {$sa**$exp2plus}] + puts stdout "...$sa**$exp2plus= $d" + set pow3 [divide_as_decimal 1 $d] + } else { + set pow3 [expr {$sa ** $exp2}] + } + + set a1 [multiply_as_decimal $pow3 $pow2] + #return $a1 + return [str2dec $a1] + + set a1 [expr {($sa ** ($sb * 10 **$eb)) * (10**($ea*$sb*(10**$eb))) }] + return [str2dec $a1] + #set er [expr {$ea + $eb}] + + return [list decimal $sr $er] + } + } + } + + #broken + proc decpow {a b} { + puts stdout "decpow $a $b" + puts stderr "REVIEW - noted as 'broken' in source'" + #a**b + #make sure that all 'x ** y' operations within 'expr' involve integer x and positive integer y. + + lassign $a _decimal sa ea + lassign $b _decimal sb eb + if {$sb eq 0} { + return [list decimal 1 0] + } else { + if {$eb < 0} { + set ebplus [expr {abs($eb)}] + #set ten2eb [dec2str [decdiv {decimal 1 0} [str2dec [expr {10**$ebplus}]] ]] + #set x [expr {$sb * $ten2eb}] + set decpow [decpow {decimal 10 0} [list decimal $ebplus 0]] + + set divscale [expr {min(2,[string length [lindex $decpow 1]])}] + puts stdout "!!! decdiv {decimal 1 0} $decpow $divscale" + set ten2eb [decdiv {decimal 1 0} $decpow -dp $divscale] + set x [decmul [list decimal $sb 0] $ten2eb] + } elseif {$eb == 0} { + set x [list decimal $sb 0] + } else { + #set x [expr {$sb * (10 **$eb)}] + set x [decmul [list decimal $sb 0] [decpow {decimal 10 0} [list decimal $eb 0]]] + } + puts stdout "sb * (10 ** eb) -> $sb * (10 ** $eb) ----> x: $x" + + #raw eqn: + # sa*(10**ea) ** sb*(10**eb) + # using rule (xy)**n = (x**n)*(y**n) + # sa**(sb * 10**eb) * (10**ea)**(sb * 10**eb) + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #---------------------------------------------- + #sa**(bdec) * 10**(ea*bdec) + + #puts stdout "--->sa **(sb*(10**eb)) * 10**(ea * sb*(10**eb)))<---" + if {$ea == 0} { + puts stdout "------------ ea = 0" + #$a = $sa (since 10**0 = 1) + + #sa **(sb * (10**eb)) + #exponent law: x**mn = (x**m)**n + #(sa ** sb) ** (10**eb) + if {$eb == 0} { + puts stdout "------------ eb = 0 sa: $sa sb: $sb" + return [list decimal [expr {$sa ** $sb}] 0] + } elseif {$eb > 0} { + #sa ** (sb * (10**eb)) + puts stdout "------------ eb > 0" + #set rdec [decpow [str2dec [expr {$sa ** $sb}]] [list decimal 1 $eb]] + set r [expr ($sa ** $sb) ** (10**$eb)] + set a1 [str2dec $r] + return $a1 + + #puts stdout "xxxxxxx eb: $eb" + #return [list decimal [expr {$sa ** $sb}] [expr {10 ** $eb}]] + } else { + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #sa**(sb * 10**eb) * 10**0 + #---------------------------------------------- + puts stdout "------------ eb < 0" + #b = sb * (10** eb) + #if eb negative then + #b = sb * (1/(10**abs(eb))) + #b = sb / (10**abs(eb)) + #negative exponent of number b - therefore equivalent of 1/(sb**abs(eb)) + if {$sa < 0} { + error "negative values not handled here.. sorry" + } + #exponent law: x**(m/n) = nth root of x**m + + #sb * 1/10**abs(eb) + + #set 10eb [decpow {decimal 10 0} [list decimal [expr {abs($eb)}] 0]] + set 10eb [expr {10**abs($eb)}] + set b [decdiv [list decimal $sb 0] [list decimal $10eb 0]] + + puts "zzz" + return [decpow [list decimal $sa 0] $b] + + + + + #puts stdout "about to find '10**abs($eb)'th root of ($sa * (10**$sb))" + #set a1 [str2dec [root [expr {$sa * (10**$sb)}] [expr {10**abs($eb)}]]] + #return $a1 + + #puts stdout "about to root ($sa ** $sb) 10**[expr abs($eb)]" + #set a1 [str2dec [root [expr {$sa**$sb}] [expr {10**abs($eb)}]]] + #return $a1 + } + } else { + puts stdout "nonzero ea = $ea" + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #---------------------------------------------- + + #puts stdout "--->sa **(x) * 10**(ea * x)<---" + + #$b < 0 whenever $sb <0 + set xstr [dec2str $x] + if {$sb < 0} { + #raising a to a negative power + + #1/sa**abs($sb * 10**eb) + set xplus [expr {abs($xstr)}] + + puts stdout "a)-->sa ** xplus => $sa ** $xplus" + #set lhs [decdiv {decimal 1 0} [str2dec [expr {$sa ** $xplus}]]] + + #--- + #work out a scale + set p10a [root $sa 10] + set p10b [root $xplus 10] + set sc [multiply_as_decimal $p10a $p10b] + lassign [split $sc .] int frac + set sc [string length $int] + #--- + + set lhs [decdiv {decimal 1 0} [decpow [list decimal $sa 0] [list decimal $xplus 0]] -dp $sc] + + } else { + #> 0 (not = 0 because we've already handled that) + puts stdout "b)" + #set lhs [expr {$sa**$x}] + #set lhs [str2dec [expr {$sa ** $x}]] + if {[string first . $xstr] < 0} { + #puts stdout "-->str2dec $sa ** $xstr" + set lhs [str2dec [expr {$sa ** $xstr}]] ;#integers + } else { + set lhs [decpow [list decimal $sa 0] $x] + } + } + puts stdout "**lhs: $lhs" + + + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #---------------------------------------------- + + if {[string first . $xstr] < 0} { + set exp2 [expr {$ea * $xstr}] ;#integers + } else { + set exp2 [dec2str [decmul [list decimal $ea 0] $x]] + } + #puts stdout " ea * x = $ea * $x = $exp2" + if {$exp2 < 0} { + puts stdout "c)" + set exp2plus [expr {abs($exp2)}] + #set d [expr {10**$exp2plus}] + #puts stdout "...10**$exp2plus= $d" + #set rhs [divide_as_decimal 1 $d] + if {$exp2plus > $::dollarcent::scale} { + set sc $exp2plus + } else { + set sc $::dollarcent::scale + } + + set rhs [decdiv {decimal 1 0} [str2dec [expr {10 ** $exp2plus}]] -dp $sc] + + + } else { + puts stdout "d) 10 ** $exp2" + #set rhs [expr {10 ** $exp2}] + set rhs [str2dec [expr {10 ** $exp2}]] + } + puts stdout "**rhs: $rhs" + + set a1 [decmul $lhs $rhs] + return $a1 + #set a1 [multiply_as_decimal $lhs $rhs] + #return [str2dec $a1] + } + } + } + + proc root {num {n 2} args} { + if {[string is integer -strict $n]} { + return [nthroot $num $n {*}$args] + } else { + return [expr {$num ** (1.0/$n)}] + } + } + + + #shifting nth root algorithm. Tested against 1st 50 or so places of root 2 - seems to work. + # - not practical for large values of n. + proc nthroot {num {n 2} args} { + set default [list -dp "" -scale $::dollarcent::scale ] + set opts [dict merge $default $args] + + lassign [split $num "."] int frac + + set dpdesired [dict get $opts -dp] + if {$dpdesired eq ""} { + set dpdesired [string length $frac] + } + + set dpworking [expr {max($dpdesired,[dict get $opts -scale])}] + + if {[string length $frac] < $dpworking} { + lappend frac [string repeat "0" [expr {$dpworking -[string length $frac]}]] + } + + set overlen [expr {[string length $int] % $n}] + if {$overlen > 0} { + set padding [string repeat 0 [expr {$n - $overlen}]] + } else { + set padding "" + } + set int $padding$int ;#padded on left with enough zeros so that we can grab groups of $n digits + + set digits [split $int {}] + + + set frac_digits [split $frac {}] + + set flen [llength $frac_digits] + if {$flen > 0} { + lappend frac_digits {*}[lrepeat [expr {$flen * ($n-1)}] 0] ;#we need another $n-1 digits for every existing digit in the fractional part + + #b + #set overlen [expr {[llength $frac_digits] % $n}] + #if {$overlen > 0} { + # lappend frac_digits {*}[lrepeat [expr {$n-$overlen}] 0] + #} + } + + lappend digits {*}$frac_digits + + + + #set overlen [expr {[string length $frac] % $n}] + #if {$overlen > 0} { + # set padding [string repeat 0 [expr $n - $overlen]] + # #append frac [string repeat "0" $flen] ;#???? + #} else { + # set padding "" + #} + #set frac $frac$padding + + #n = degree of root to be extracted = (10**eb) + #x = radicand processed thus far + #y = root extracted thus far + #r = remainder + #a = next n digits of the radicand + #B = next digit of the root + + #invariant y**n + r = x + #ie. y is the largest integer less than the nth root of x and r is the remainder. + + set x 0 + set y 0 + set r 0 + + #base = 10 - base of number system we're using. + #puts stdout "intfrac: '$int$frac'" + #puts stdout $digits + set nblock "" + foreach d $digits { + append nblock $d + if {[string length $nblock] < $n} { + continue + } + #puts stdout ". $nblock ." + scan $nblock %d a + set nblock "" ;#ready to build nex one. + + #set x2 [expr {(10**$n) * $x + $a}] ;#running power (radicand processed so far) + + #find largest integer B such that (10y + B)**n <= (10**n)x + a + #---- + # for square root case (n =2) + # 100y**2 + 20yB +B**2 <= 100x + a + #---- + #B will always be less than base ie B < 10 (see http://en.wikipedia.org/wiki/Shifting_nth_root_algorithm ) + for {set B 0} {$B < 10} {incr B} { + if {(((10 * $y + $B) ** $n) - (10**$n * $y**$n)) <= (((10**$n) * $r) + $a)} { + #ok + } else { + #overshot + break + } + } + incr B -1 + set y2 [expr {(10 * $y) + $B}] + set r2 [expr {(10**$n)*$r + $a - ((10*$y + $B)**$n - (10**$n)*($y**$n))}] + + set y $y2 + set r $r2 + + #puts -nonewline $B + } + #puts stdout "" + #puts stdout "y: $y" + + #set a [expr {$y / pow(10,([llength $frac_digits]/$n))}] + #puts stdout "answer1: $a" + + #set sc [expr {[llength $frac_digits] / $n}] + #set sc $::dollarcent::scale + set sc $dpdesired + + set y [dec2str [decdiv [str2dec $y] [str2dec [expr {10**([llength $frac_digits]/$n)}] ] -dp $sc] ] + return $y + } + + + #decimal product + proc decmul {a b} { + #puts stdout "decmul $a $b" + lassign $a _decimal sa ea ;#significand-a exponent-a + lassign $b _decimal sb eb + + #integer operations - not floating point. + set sr [expr {$sa * $sb}] + set er [expr {$ea + $eb}] + + return [list decimal $sr $er] + } + + #decimal division + #rounds last digit. (automatically chooses scale large enough to provide additional 2 digits to determine rounding value) + proc decdiv {a b args} { + variable scale + set default [list -dp ""] + set opts [dict merge $default $args] + set dpdesired [dict get $opts -dp] + + if {$dpdesired ne ""} { + #set sc $dpdesired + set dp_exact 1 + } else { + set dpdesired $scale + set dp_exact 0 ;#we're allowed to trim repeat rhs zeros + } + if {$dpdesired >= $scale} { + set sc [expr {$dpdesired + 2}] ;#2 additional digits required for rounding + } else { + if {($scale - $dpdesired) == 1} { + set sc [expr {$dpdesired + 2}] ;#scale is just above dpdesired + } else { + set sc $scale + } + } + #set sc [expr {max($dpdesired,$scale)}] + + lassign $a _decimal sa ea ;#sa significand-a, ea exponent-a + lassign $b _decimal sb eb + + if {$ea >=0} { + set dpa 0 + } else { + set dpa [expr {abs($ea)}] + } + if {$eb >=0} { + set dpb 0 + } else { + set dpb [expr {abs($eb)}] + } + + + set ab_highest_dp [expr {max($dpa,$dpb)}] + set ab_lowest_dp [expr {min($dpa,$dpb)}] + set ab_diff_dp [expr {$dpb - $dpa}] + + set dpr [expr {$sc - $ab_diff_dp}] ;#decimal places in the result. + #puts stdout "sc:$sc - (dpb:$dpb - dpa:$dpa) = $sc - [expr {$dpb - $dpa}]. dp result: $dpr" + + set desired_min_dp [expr {min($dpdesired,$ab_highest_dp)}] + + #sc must not be < $dpa, or the calculation of sr1 below will not be a pure integer operation! + + #!todo - sc should be large enough that dpdesired+2 dp of digits always available for accurate/consistent rounding. + # (so we can differentiate betw .50 and .5x when using a rounding method for which this makes a difference) + + if {$dpr != ($dpdesired + 2)} { + incr sc [expr {max((-$sc + $dpa),$ab_diff_dp)}] + set dpr [expr {$sc - $ab_diff_dp}] ;#decimal places in the result. + + if {$dpr < $desired_min_dp} { + incr sc [expr {$desired_min_dp - $dpr}] + set dpr [expr {$sc - $ab_diff_dp}] ;#decimal places in the result. + } + } + #puts stdout "sc:$sc - ($dpb - $dpa) = $sc - [expr {$dpb - $dpa}]. dp result: $dpr" + + #puts stdout "calc: $sa * (10**$sc) / $sb" + set sr1 [expr {$sa * (10**$sc) / $sb}] ;#integer maths. ($sc always positive) Result will not be in scientific notation, and will not have a radix point. + if {$sr1 eq 0} { + set sr 0 + set er 0 + } else { + #jmn + #round if dpdesired < dpr + #(this result may be used as an intermediate for other calcs - not appropriate to aggressively round or limit sigfigs unless explicitly set using dpdesired) + if {$dpdesired >= $dpr} { + set sr $sr1 + set er [expr {$ea - $eb - $sc}] + } else { + set excess [expr {$dpr - $dpdesired}] + set sr [string range $sr1 0 end-$excess] + + set tail [string range $sr1 end-[expr {$excess -1}] end-[expr {$excess -2}]] + lassign [split $tail {}] e1 e2 + #puts stdout "dpr: $dpr len sr1:[string length $sr1] sr1: $sr1 sr: $sr tail: $tail" + + if {$e1 >= 6} { + incr sr + } elseif {$e1 == 5} { + if {$e2 == 0} { + #round based on current rounding method. + set sr [do_round $sr $e1 $e2] + + #round half up + #incr sr + } else { + #>50 always round up. + incr sr + } + } + set er [expr {$ea - $eb - $sc +$excess}] + } + } + return [list decimal $sr $er] + } + + + #-------------------------------------------- + #!todo - something. + # - what about countries which use USD or AUD etc instead of having their own currency? + variable currency_symbol + variable currency_country + + #e.g http://coinmill.com/sources.html + #we use smallestmajor for their term "Smallest Currency Unit" + #This is potentially useful to implement 'cash' rounding + #- but it should be noted that this is changeable as countries withdraw their smallest units again and again whilst they inflate away savings. + + dict set currency_symbol AUD [list domain Australia majorunit dollar minorunit cent smallestmajor 0.05 symbol AUD] + dict set currency_symbol AFN [list domain Afghanistan majorunit afghani minorunit "" smallestmajor 1 symbol AFN] + dict set currency_symbol EUR [list domain "European Union" majorunit euro minorunit cent smallestmajor 0.01 symbol EUR] + dict set currency_symbol GBP [list domain "UK" majorunit pound minorunit cent smallestmajor 0.01 symbol GBP] + dict set currency_symbol NZD [list domain "New Zealand" majorunit dollar minorunit cent smallestmajor 0.10 symbol NZD] + dict set currency_symbol USD [list domain "United States" majorunit dollar minorunit cent smallestmajor 0.01 symbol USD] + dict set currency_symbol {L$} [list domain "Linden" majorunit dollar minorunit cent smallestmajor 0.01 symbol {L$} note "Second Life"] + + #For cryptocurrencies - smallestmajor could be considered to be the smallest divisibility - but there are often network limits + #on what is in practice the smallest transferable amount. (e.g transaction fees, variable based on network capacity/demand and relative value of the currency) + dict set currency_symbol BTC [list domain "Bitcoin" majorunit BTC minorunit satoshi smallestmajor 0.00000001 symbol "\U20BF"] + dict set currency_symbol BCH [list domain "Bitcoin Cash" majorunit BCH minorunit satoshi smallestmajor 0.00000001 symbol "BCH"] + #eth smallest major 1e-18 'wei' - but 'gwei' 1e-9 is commonly uses as 'Ethereum gas' is paid in gwei + dict set currency_symbol ETH [list domain "Ethereum" majorunit ETH minorunit wei smallestmajor 0.000000000000000001 symbol "ETH"] + #-------------------------------------------- + + + + + #shortcut methods - from dollars to various fractional-cent units. + #interp alias {} ${ns}::d2c {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-0 {*}$args}}] + interp alias {} ${ns}::d2c {} ::dollarcent::convert d to c-0 + #interp alias {} ${ns}::d2xc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-1 {*}$args}}] + interp alias {} ${ns}::d2xc {} ::dollarcent::convert d to c-1 + #interp alias {} ${ns}::d2cc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-2 {*}$args}}] + interp alias {} ${ns}::d2cc {} ::dollarcent::convert d to c-2 + #interp alias {} ${ns}::d2mc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-3 {*}$args}}] + interp alias {} ${ns}::d2mc {} ::dollarcent::convert d to c-3 + #interp alias {} ${ns}::d2_xc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-4 {*}$args}}] + interp alias {} ${ns}::d2_xc {} ::dollarcent::convert d to c-4 + #interp alias {} ${ns}::d2_cc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-5 {*}$args}}] + interp alias {} ${ns}::d2_cc {} ::dollarcent::convert d to c-5 + #interp alias {} ${ns}::d2_mc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-6 {*}$args}}] + interp alias {} ${ns}::d2_mc {} ::dollarcent::convert do to c-6 + namespace export d2c d2xc d2cc d2mc d2_xc d2_cc d2_mc + + #shortcut methods - from various fractional-cent units to dollars. + #interp alias {} ${ns}::c2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-0 to d {*}$args}}] + interp alias {} ${ns}::c2d {} ::dollarcent::convert c-0 to d + #interp alias {} ${ns}::xc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-1 to d {*}$args}}] + interp alias {} ${ns}::xc2d {} ::dollarcent::convert c-1 to d + #interp alias {} ${ns}::cc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-2 to d {*}$args}}] + interp alias {} ${ns}::cc2d {} ::dollarcent::convert c-2 to d + #interp alias {} ${ns}::mc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-3 to d {*}$args}}] + interp alias {} ${ns}::mc2d {} ::dollarcent::convert c-3 to d + #interp alias {} ${ns}::_xc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-4 to d {*}$args}}] + interp alias {} ${ns}::_xc2d {} ::dollarcent::convert c-4 to d + #interp alias {} ${ns}::_cc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-5 to d {*}$args}}] + interp alias {} ${ns}::_cc2d {} ::dollarcent::convert c-5 to d + #interp alias {} ${ns}::_mc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-6 to d {*}$args}}] + interp alias {} ${ns}::_mc2d {} ::dollarcent::convert c-6 to d + namespace export c2d xc2d cc2d mc2d _xc2d _cc2d _mc2d + namespace export convert roundcents + + #namespace export root + #namespace export decadd decsub decmul decdiv decpow str2dec dec2str sigfigs + #namespace export roundcents multiply_as_decimal divide_as_decimal add_as_decimal sub_as_decimal power_as_decimal + + + + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace dollarcent ---}] +} + +#+ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval dollarcent::lib { + namespace export {[a-z]*}; # Convention: export all lowercase + namespace path [namespace parent] + #*** !doctools + #[subsection {Namespace dollarcent::lib}] + #[para] Secondary functions that are part of the API + #[list_begin definitions] + + #proc utility1 {p1 args} { + # #*** !doctools + # #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] + # #[para]Description of utility1 + # return 1 + #} + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace dollarcent::lib ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[section Internal] +namespace eval dollarcent::system { + #*** !doctools + #[subsection {Namespace dollarcent::system}] + #[para] Internal functions that are not part of the API + + + +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide dollarcent [namespace eval dollarcent { + variable pkg dollarcent + variable version + set version 1.1 +}] +return + +#*** !doctools +#[manpage_end] + diff --git a/src/vendormodules/include_modules.config b/src/vendormodules/include_modules.config index a9c143af..f46ecd5d 100644 --- a/src/vendormodules/include_modules.config +++ b/src/vendormodules/include_modules.config @@ -13,6 +13,7 @@ set local_modules [list\ c:/repo/jn/tclmodules/tomlish/modules tomlish\ c:/repo/jn/tclmodules/tomlish/modules test::tomlish\ c:/repo/jn/tclmodules/dictn/modules dictn\ + c:/repo/jn/tclmodules/dollarcent/modules dollarcent\ ] set fossil_modules [dict create\ diff --git a/src/vfs/_vfscommon.vfs/modules/dollarcent-1.1.tm b/src/vfs/_vfscommon.vfs/modules/dollarcent-1.1.tm new file mode 100644 index 00000000..4c69c854 --- /dev/null +++ b/src/vfs/_vfscommon.vfs/modules/dollarcent-1.1.tm @@ -0,0 +1,1522 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2025 +# +# @@ Meta Begin +# Application dollarcent 1.1 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# doctools header +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[manpage_begin dollarcent_module_dollarcent 0 1.1] +#[copyright "2025"] +#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] +#[moddesc {-}] [comment {-- Description at end of page heading --}] +#[require dollarcent] +#[keywords module] +#[description] +#[para] - + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section Overview] +#[para] overview of dollarcent +#[subsection Concepts] +#[para] - + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[subsection dependencies] +#[para] packages used by dollarcent +#[list_begin itemized] + +package require Tcl 8.6- +#*** !doctools +#[item] [package {Tcl 8.6-}] + + +#*** !doctools +#[list_end] + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +#*** !doctools +#[section API] + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Base namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + +namespace eval dollarcent { + #only export main api - see at end of namespace + #todo - tidy up functions in main namespace and move some to lib etc + #namespace export {[a-z]*}; # Convention: export all lowercase + #variable xyz + + #*** !doctools + #[subsection {Namespace dollarcent}] + #[para] Core API functions for dollarcent + #[list_begin definitions] + + variable strict 1 ;#0|1|2 + # 0 = ignore intermixing of conversions with different fractional-cent units. + # 1 = warn with msg to stderr about intermixing + # 2 = raise error + + #2025 - todo + #WARNING - statefulness of variables such as strict,units,rounding_method - suggest we should use an object-like interface - otherwise concurrent use in different contexts is risky. + + variable units "" ;#last active units used for fractional-cents. Raise error or warning if attempt to use a different conversion unit without explicitly resetting. + + proc units {{fractional_cent_unit "-"}} { + variable units + if {$fractional_cent_unit eq "-"} { + return $units + } else { + if {$fractional_cent_unit eq ""} { + #allow 'reset' so that whatever next unit is used in conversion will become next units value. + set units "" + } else { + foreach {a b} {c c-0 xc c-1 cc c-2 mc c-3 _xc c-4 _cc c-5 _mc c-6} { + if {$fractional_cent_unit eq $a} { + set fractional_cent_unit $b + break + } + } + if {![string match "c-*" $fractional_cent_unit]} { + error "Expected fractional cent unit such as 'c' 'xc' 'mc' '_xc' or 'c-1' 'c-2' 'c-3' 'c-4' etc - got: '$fractional_cent_unit'" + } + set units $fractional_cent_unit + } + } + } + + variable rounding_methods + + set rounding_methods [dict create] + dict set rounding_methods halfeven {unbiased statisticians bankers convergent dutch gaussian} + dict set rounding_methods halfup {up euro} + dict set rounding_methods halfdown {down} + dict set rounding_methods halfawayfromzero {} + dict set rounding_methods halftowardszero {} + dict set rounding_methods unsupported {stochastic swedish cash} + #'cash' method (sometimes called swedish) depends on smallest unit of currency in play for a particular jurisdication/country + + variable rounding_method halfup ;#default rounding 0.5 upwards + + proc roundingmethods {} { + variable rounding_methods + return $rounding_methods + } + + #--------------------------------- + #rounding implementations + + proc do_round_halfeven {num e1 e2} { + if {($e1 eq "5") && ($e2 eq "0")} { + set last [string range $num end end] + if {($last % 2) == 0} { + #even + incr num + } + } elseif {$e1 eq "5"} { + incr num + } + return $num + } + + proc do_round_halfup {num e1 e2} { + if {$e1 eq "5"} { + incr num + } + return $num + } + proc do_round_halfdown {num e1 e2} { + if {$e1 eq "5"} { + if {$e2 ne "0"} { + # ${num}5x is > ${num}50 round up. + incr num + } + #exactly ${num}50 - leave num as is. (ie round down) + } + return $num + } + proc do_round_halfawayfromzero {num e1 e2} { + if {$num > 0} { + set num [do_round_halfup $num $e1 $e2] + } else { + if {$e1 eq "5"} { + if {$e2 ne "0"} { + incr num -1 + } + } + } + return $num + } + proc do_round_halftowardszero {num e1 e2} { + if {$num > 0} { + if {$e1 eq "5"} { + if {$e2 ne "0"} { + incr num -1 + } + } + } else { + set num [do_round_halfup $num $e1 $e2] + } + return $num + } + #---------------------------------- + proc do_round {num e1 e2} { + variable rounding_method + variable rounding_methods + set meth "-" + if {$rounding_method ni [dict keys $rounding_methods]} { + foreach m [dict keys $rounding_methods] { + set aliases [dict get $rounding_methods $m] + if {$rounding_method in $aliases} { + if {"::dollarcent::do_round_$m" in [info commands ::dollarcent::*]} { + set meth $m + } + break + } + } + } else { + set meth $rounding_method + } + if {$meth eq "-"} { + error "rounding method '$rounding_method' not supported" + } + + do_round_$meth $num $e1 $e2 + } + + #2010-04 - roundingmethod not yet in use. review. Get the basic roundhalfup version working and fully tested first! + proc roundingmethod {args} { + variable rounding_method + variable rounding_methods + if {![llength $args]} { + return $rounding_method + } else { + set allmethods [list] + foreach m [dict keys $rounding_methods] { + if {$m ne "unsupported"} { + lappend allmethods $m + lappend allmethods {*}[dict get $rounding_methods $m] + } + } + + set desiredmethod [lindex $args 0] + if {$desiredmethod ni $allmethods} { + error "rounding method '$desiredmethod' is not supported" + } + set rounding_method $desiredmethod + } + } + + + set ns [namespace current] + + + #proc convert {amount source TO target args} {} ;#old v 1.0 signature - not easy to alias or pipeline + package require punk::args + punk::args::define { + @id -id ::dollarcent::convert + @cmd -name dollarcent::convert -help\ + "Convert between a floating point number representing a value specified in the + major unit of a currency - to an integer representing the number of fractional + parts of the minor unit. + e.g if dealing with dollars and cents and using c-level cc (c-2) + convert between a dollar figure like 1.00 + and a quantity of fractional-cents 10000" + @form -form dollar + @leaders -min 3 -max 3 + double_amount_type -type string -choices {dollars} -help\ + "'dollars' means convert FROM the large unit of whatever currency is + being processed" + to -type literal(TO)|literal(to) + target_type -type string -choicerestricted 0 -choices {c xc cc mc _xc _cc _mc}\ + -choicelabels { + "c"\ + "equiv: c-0" + "xc"\ + "equiv: c-1" + "cc"\ + "equiv: c-2" + "mc"\ + "equiv: c-3" + "_xc"\ + "equiv: c-4" + "_cc"\ + "equiv: c-5" + "_mc"\ + "equiv: c-6" + } -help\ + "The scale of the small unit of currency for calculation purposes. + e.g c (c-0) is 1 major unit to 100 + mc (c-3) is 1 major unit to 100_000 + _mc (c-6) is 1 major unit to 100_000_000 + + c happens to correspond to USD $1 to 100c + and _mc happens to correspond to 1BTC to 100Million Satoshi, + but generally you'd want a higher level than the number of + small units per major unit so that rounding errors are reduced + to well below the level of the minor currency unit. + " + @opts + -strict -type integer -default 1 -choices {0 1 2}\ + -choicelabels { + 0 {Silence even if units (c-level) used don't match last used} + 1 {Warn on stderr if units have changed} + 2 {Error if units have changed} + } -choicecolumns 1 + @values -min 1 -max 1 + amount -type double -help\ + "Quantity of the largest units of the currency (a conceptual 'Dollar' amount). + For another currency this might be Euros or BTC. + It is specified as a floating-point number with at least 2 decimal places, + but the maximum allowed decimal places is determined by the target_type." + + + @form -form cent + @leaders -min 3 -max 3 + integer_amount_type -type string -choicerestricted 0 -choices {c xc cc mc _xc _cc _mc}\ + -choicelabels { + "c"\ + "equiv: c-0" + "xc"\ + "equiv: c-1" + "cc"\ + "equiv: c-2" + "mc"\ + "equiv: c-3" + "_xc"\ + "equiv: c-4" + "_cc"\ + "equiv: c-5" + "_mc"\ + "equiv: c-6" + } -help\ + "The scale of the small unit of currency for calculation purposes. + e.g c (c-0) is 1 major unit to 100 + mc (c-3) is 1 major unit to 100_000 + _mc (c-6) is 1 major unit to 100_000_000 + + c happens to correspond to USD $1 to 100c + and _mc happens to correspond to 1BTC to 100Million Satoshi, + but generally you'd want a higher level than the number of + small units per major unit so that rounding errors are reduced + to well below the level of the minor currency unit. + " + to -type literal(TO)|literal(to) + target_type -type string -choices {dollars} -help\ + "'dollars' means convert TO the large unit of whatever currency is + being processed" + + @opts + -strict -type integer -default 1 -choices {0 1 2}\ + -choicelabels { + 0 {Silence even if units (c-level) used don't match last used} + 1 {Warn on stderr if units have changed} + 2 {Error if units have changed} + } -choicecolumns 1 + @values -min 1 -max 1 + amount -type double -help\ + "Integer quantity being used for conversion to the major unit amount. + The same scale (c-level) must be used in converting to and from large + and small units for any calculations to make sense." + + + } + proc convert {args} { + set amount_type [tcl::prefix::match -error "" {dollars} [lindex $args 0]] + if {$amount_type eq "dollars"} { + set argd [punk::args::parse $args -form dollar withid ::dollarcent::convert] + } else { + set amount_type [lindex $args 0] + set argd [punk::args::parse $args -form cent withid ::dollarcent::convert] + } + + variable strict ;#whether and how to alert programmer if subsequent calls use different fractional-cent units. + variable units ;#we remember the fractional cent units used in previous calls and disallow different units unless explicitly set with dollarcent::units. + + lassign [dict values $argd] leaders opts values received + + set bestrict [dict get $opts -strict] + + set target_type [dict get $leaders target_type] + set amount [dict get $values amount] + + #c-level aliases + set c_aliases [dict create c c-0 xc c-1 cc c-2 mc c-3 _xc c-4 _cc c-5 _mc c-6] + + #puts stdout "->convert $amount $amount_type $TO $target" + if {$amount_type eq {dollars}} { + # where c-0 = cents, c-1 represents cents * 10-1 or 'tenths of a cent', c-2 = 'hundredths of a cent' etc. + #c, xc, cc, mc are shortcuts for these - designed to match the convenience functions dc2c dc2xc dc2cc dc2mc dc2_xc, c2dc xc2dc mc2dc etc + # ie roman numeralesque + # x = 10 + # c = 100 (leading char only - trailing one means cents!) + # m = 1000 + # _x = 10000 (underscore to represent 'overbar' used in roman numerals to indicate multiplication by 1000) + # _c = 100000 + # _m = 1000000 + + #dc2_xc dc2_mc + if {[dict exists $c_aliases $target_type]} { + set target_type [dict get $c_aliases $target_type] + } + + if {$units eq ""} { + #1st use in this interp - ok - just set the units. + set units $target_type + } else { + if {$target_type ne $units} { + set msg "The fractional-cent unit already in use is '$units' but the current conversion specified: '$target_type'. Call 'dollarcent::units ' if you wish to change the size of the fractional-cent in use." + append msg "\ne.g 'c-0' = cents, 'c-1' or 'xc' = tenths of a cent, 'c-2' or 'cc' = hundredths of a cent, 'c-3' or 'mc' = thousandths, 'c-4' or '_xc = 10-thousandths etc." + if {$bestrict == 1} { + puts stderr $msg + } elseif {$bestrict == 2} { + error $msg + } else { + #ignore - but *don't* reset units - this may have been a 'oneshot' use of a different unit by use of the -strict 0 option to 'convert'. + #We require that the programmer specifically set the units. + } + } + } + + + set dollars $amount + + lassign [split $target_type -] _c power + #if {$power > 20} { + # error "dollarcent package probably can't handle such small fractions of a cent. '10 to the -20' may be the limit. Check code in dollarcent module and adjust this test as necessary." + #} + + #puts stdout "power is $power" + #puts stdout "dollars: $dollars" + set format "%lld.%1d%1d[string repeat %1d $power]%s" ;#note - this is lower case ll not numeral 11! + + set vars [lrepeat [expr {$power + 2}] _c] ;#just a list of placeholder vars for the scan function to + + set partcount [scan $dollars $format bucks {*}$vars _disallowed] + set dpcount [expr {$partcount -1}] ;#subtract the LHS to get the count of number of decimal places. + if {$dpcount < 2 || ($dpcount > (2 + $power))} { + return -code error "Incorrect precision for target type '$target_type'. Dollar amount must be a decimal number with between 2 and [expr {2 + $power}] digits following the decimal point." + } + + #------------------------------- + #Note: We could do this.. but this is a floating point multiplication. + # - for $power = 0, this seems to work up to about $(2**45).34 ie 35trillion,184billion,372million,88thousand,832 dollars + # - if the chosen base unit is 10 thousandths of a cent, then rounding errors will occur at somewhere around 550billion dollars. + # - Presumably rounding errors regarding a cent for such large sums are not an issue for the vast majority of use-cases - but it's the intention that this module + # eventually be accurate and flexible enough to be used in all sorts of situations e.g for some pseudo-currency in a virtual-world/game economy or perhaps even in a hyperinflationary environment. + # (No fitness for use in such situations is implied) + #return [expr {round($dollars*100*(10**$power))}] + #------------------------------- + + + set subcents [decmul [str2dec $dollars] [str2dec [expr {100*(10**$power)}]]] + #set result [expr {round([dec2str $subcents])}] ;#don't do this - round will do funny things for large numbers. + set result [dec2str $subcents] + + lassign [split $result .] whole part + set c1 [string range $part 0 0] + if {$c1 >= 5} { + if {$c1 > 5} { + if {$whole > 0} { + incr whole + } else { + incr whole -1 + } + } else { + #c1 exactly 5 + set tail [string range $part 1 end] + if {([string length $tail] ==0) || ($tail == 0)} { + #entirety of 'part' is equivalent to exactly 0.5 + ##!todo - round based on currently active roundingmethod + if {$whole > 0} { + incr whole + } else { + incr whole -1 + } + } else { + #there is something non-zero folowing c1 + #therefore round up - no matter what the currently active rounding method. + if {$whole > 0} { + incr whole + } else { + incr whole -1 + } + } + } + } + return $whole + + } else { + #we're converting fractional cents to dollars & cents. + + if {[string match "-*" $amount]} { + set amount [string range $amount 1 end] + set sign - + } else { + set sign "" + } + + set int_pennyparts $amount + if {[dict exists $c_aliases $amount_type]} { + set amount_type [dict get $c_aliases $amount_type] + } + + if {$units eq ""} { + #1st use in this interp - ok - just set the units. + set units $amount_type + } else { + if {$amount_type ne $units} { + set msg "The fractional-cent (c-level) unit already in use is '$units' but the current conversion specified: '$amount_type'. Call 'dollarcent::units ' if you wish to change the size of the fractional-cent in use." + append msg "\ne.g 'c-0' = cents, 'c-1' or 'xc' = tenths of a cent, 'c-2' or 'cc' = hundredths of a cent, 'c-3' or 'mc' = thousandths, 'c-4' or '_xc = 10-thousandths etc." + if {$bestrict == 1} { + puts stderr $msg + } elseif {$bestrict == 2} { + error $msg + } else { + #ignore - but *don't* reset units - this may have been a 'oneshot' use of a different unit by use of the -strict 0 option to 'convert'. + #We require that the programmer specifically set the units. + } + } + } + + + + lassign [split $amount_type -] _c power + if {$power > 19} { + error "dollarcent package probably can't handle such small fractions of a cent. '10 to the -19' may be the limit. Check code in dollarcent module and adjust this test as necessary." + } + + + if {$target_type ne {dollars}} { + error "For conversion amount_type '$amount_type' - unknown target type '$target_type'" + } + #format string note - this is lowercase ll not number 11! + if {[scan $int_pennyparts %lld%c int_pennyparts _disallowed] == 1} { + #---------------- + #NOTE! 'format %.2f' does implicit rounding - and also varies in behaviour across platforms. This is not suitable for use here. + #return [format %.2f [expr {$int_pennyparts /(100.0 *(10**$power))}]] ;#NOTE - %.2f rounds in a strange manner - and is inconsistent across tcl platforms! + #----------------- + + #----------------- + #return [expr {$int_pennyparts /(100.0 *(10**$power))}] + #----------------- + + set r [decdiv [str2dec $int_pennyparts] [str2dec [expr {100*(10**$power)}]]] + #puts stdout "r -> $r" + + lassign $r _dec significand exp + + #trailing zeroes not useful here + #(normally trailing zeroes are sigfigs) + set r_significand [string reverse $significand] + + set exp2 $exp + foreach z [split $r_significand ""] { + if {$z eq "0"} { + incr exp2 + } else { + break + } + } + set significand2 [string trimright $significand "0"] + + #return [dec2str [list decimal $significand $exp]] ;#dollar result + set result [dec2str [list decimal $significand2 $exp2]] ;#dollar result + + if {[string first . $result] < 0} { + set result $result.00 + } else { + lassign [split $result .] dollarpart centpart + if {[string length $centpart] == 1} { + set centpart ${centpart}0 + } + if {[string length $dollarpart] > 1} { + set dollarpart [string trimleft $dollarpart 0] + } + set result $dollarpart.$centpart + } + return $sign$result + } else { + return -code error "Only an integer number of units can be converted to dollars and cents. Please round before converting. amount:'$int_pennyparts'" + } + } + } + + proc roundcents {dollars} { + lassign [split $dollars .] bucks cents + if {[string length $cents] < 2} { + return -cdoe error "'roundcents' requires that dollar amount be given with at least 2 decimal places" + } + + set decimal_dollaramount [str2dec $dollars] + set decimal_100 [str2dec 100] + set decimal_cents [decmul $decimal_dollaramount $decimal_100] + + set fpcents [dec2str $decimal_cents] + #puts stdout "fpcents-> $fpcents" + + lassign [split $fpcents .] wholecents frac + set frac1 [string range $frac 0 0] + set fractail [string range $frac 1 end] + if {$frac1 >= 5} { + if {$frac1 == 5} { + if {([string length $fractail] == 0) || ($fractail == 0)} { + #entirety of 'frac' is equivalent to .5 + #use active rounding method to round + #!todo - roundingmethods + if {$wholecents > 0} { + incr wholecents + } else { + incr wholecents -1 + } + } else { + #the x in .5x is non-zero - always round up no matter what rounding method is in effect. + if {$wholecents > 0} { + incr wholecents + } else { + incr wholecents -1 + } + } + } else { + if {$wholecents > 0} { + incr wholecents + } else { + incr wholecents -1 + } + } + } + + #set cents [expr {round($fpcents)}] + + set d [c2d -strict 0 $wholecents] + return $d + } + + #normalize a string representing dollars and cents, from a US,AU,NZ etc perspective + #ie doesn't cater for european conventions of comma as decimal separator. + #This normalization from the perspective of this library: + # conversion from other conventions such as European should be done at input/output of whole process. (using locale etc) + + # e.g '$2.1' -> '2.10' + #Must also cope with underscores and commas in combination with leading zeros + #Must not lose decimal places e.g $10.33335 -> 10.33335 + #But we convert both 10. and 10.0 to 10.00 (not correct from a sigfig perspective - but more useful) + proc normaldollars {dollaramount} { + set amount [string trim $dollaramount] + set amount [string map {{-$} {$-}} $amount] ;# -$2.10 equivalent to $-2.10 + if {[string index $amount 0] eq {$}} { + } + set amount [string trimleft $amount {$}] + if {$amount in {"" "-"}} { + error "'$dollaramount' is not in a format that normaldollars can recognize" + } + + if {[string match "-*" $amount]} { + set amount [string range $amount 1 end] + set sign - + } else { + set sign "" + } + if {[string match ".*" $amount]} { + set amount 0$amount + } + if {[string match "*." $amount]} { + set amount ${amount}00 + } + + if {[string first . $amount] < 0} { + set amount $amount.00 + } else { + lassign [split $amount .] dollarpart centpart + if {[string length $centpart] == 1} { + set centpart ${centpart}0 + } + if {[string length $dollarpart] > 1} { + set dollarpart [string trimleft $dollarpart 0] + } + set amount $dollarpart.$centpart + } + + return $sign$amount + } + + + variable scale 38 ;#fairly arbitrary - but 28 supposedly matches that of Python. 'ISO COBOL 2002 standard' uses 32, Transact SQL has a max of 38. + variable maxmantisse [expr {10**$scale}] + variable bndmantisse [expr {10*$maxmantisse}] + proc set_scale {newscale} { + variable scale + variable maxmantisse + variable bndmantisse + + set scale $newscale + set maxmantisse [expr {10**$scale}] + set bndmantisse [expr {10*$maxmantisse}] + } + + + #---------------------------------------------------------------- + # Rescale -- + # Rescale the number (using proper rounding) + # + # Arguments: + # decimal in the form {decimal significand exponent} + # + # Result: + # Rescaled number (as a list) + # + proc decrescale {decimal {newscale ""}} { + variable maxmantisse + variable bndmantisse + + lassign $decimal _decimal significand exponent + + if { abs($significand) <= $maxmantisse } { + return [list decimal $significand $exponent] + } + + set rest [expr {$significand % 10}] ;#for case where $maxmantisse < abs($significand) <= $bndmantisse + while { abs($significand) > $bndmantisse } { + set rest [expr {$significand % 10}] + set significand [expr {$significand/10}] + incr exponent + } + + + if { $rest > 5 } { + if { $significand > 0 } { + incr significand + } else { + incr significand -1 + } + } elseif { $rest == 5 } { + #halfup rounding + if { $significand > 0 } { + incr significand + } else { + incr significand -1 + } + + #bankers rounding + #if { ($significand/10) % 2 == 1 } { + # incr significand + #} + } + + return [list decimal $significand $exponent] + } + + proc str2dec {string} { + set pos [string first . $string] + if {$pos < 0} { + #significand = mantissa + set significand $string + set significand [string trimleft $string "0"] + if {$significand eq ""} { + set significand 0 + } + set exponent 0 + } else { + set fraction [string range $string $pos+1 end] + set significand [string trimleft [string map {. ""} $string] 0] + if {$significand eq ""} { + set significand 0 + } + set exponent [expr {-[string length $fraction]}] + } + return [list decimal $significand $exponent] + } + + proc dec2str {decimal_as_list} { + #puts stdout "==>dec2str $decimal_as_list" + lassign $decimal_as_list _decimal significand exponent + if {![string length $significand]} { + set significand 0 + } + if {$significand < 0} { + set sign - + } else { + set sign "" + } + set significand [string map {- ""} $significand] + if {$exponent > 0} { + if {$significand == 0} { + set string 0 + } else { + set string $sign$significand[string repeat 0 $exponent] + } + } else { + set digits [string length $significand] + + set exponent [expr {abs($exponent)}] + if {$digits >= $exponent} { + if {$exponent == 0} { + set string $sign$significand + } else { + if {$digits == $exponent} { + #e.g 10 * 10**-2 + set lhs "0" + } else { + set lhs [string range $significand 0 [expr {$digits-$exponent-1}]] + } + set string $sign$lhs.[string range $significand [expr {$digits-$exponent}] end] + } + } else { + set string ${sign}0.[string repeat 0 [expr {$exponent-$digits}]]$significand + } + } + return $string + } + + proc multiply_as_decimal {a b args} { + return [dec2str [decmul [str2dec $a] [str2dec $b] {*}$args]] + } + proc divide_as_decimal {a b args} { + return [dec2str [decdiv [str2dec $a] [str2dec $b] {*}$args]] + } + proc add_as_decimal {a b args} { + return [dec2str [decadd [str2dec $a] [str2dec $b] {*}$args]] + } + proc power_as_decimal {a b args} { + return [dec2str [decpow [str2dec $a] [str2dec $b] {*}$args]] + } + + + #round 'val' to 'dp' decimal places + proc dpround {val dp} { + error "not implemented" + } + #round 'val' to 'sigfigs' significant figures + proc sigfiground {val sigfigs} { + error "not implemented" + } + + #significant figures 'are a rather crude way of tracking precision' + # see http://scienceblogs.com/goodmath/2009/03/basics_significant_figures.php + # - NOTE - to review - are sigfigs really even relevant to currency work? + proc sigfigs {floatstr} { + + #1) all non-zero digits are considered significant + #2) zeros between non-zeros are significant. + #3) leading zeros not significant. + + # - for this function - we will assume trailing zeros in numbers with no decimal point are insignificant. + # There is an ambiguity here because it could be that the zeros are significant but the value just happens to be round.. + # we adopt the convention that a trailing . on the input will indicate that the previous zeros are significant. + # e.g "100" has 1 sigfigs but "100." has 3 sigfigs. + + #rules when performing calculations: + #a) for multiplication and division the final answer should contain only as many sigfigs as the number with the *least* number of sigfigs. + #b) for addition and subtraction - keep the number of sigfigs in the input with the smallest number of *decimal places* + + + set i 0 + set s 0 + set in_sigfigs 0 + set sigs "" ;#significant digits + set dp 0 + set in_dp 0 + if {[string first . $floatstr] >= 0} { + set has_radix 1 ;#radix is a more general term for decimal point. + } else { + set has_radix 0 + } + if {[string match "-*" $floatstr]} { + set floatstr [string map {- ""} $floatstr] + incr i + } + foreach c [split $floatstr ""] { + if {$c eq "0"} { + if {$in_sigfigs} { + append sigs "0" + } + } else { + if {$c ne "."} { + if {!$in_sigfigs} { + set in_sigfigs 1 + set s $i + } + append sigs $c + } else { + set in_dp 1 + } + } + if {$c ne "."} { + if {$in_dp} { + incr dp + } + } + incr i + } + if {!$has_radix} { + #no radix. We will consider trailing zeros to be insignificant. + set sigs [string trimright $sigs "0"] + } + + return [list count [string length $sigs] startindex $s digits $sigs dp $dp] + } + + #decimal sum + proc decadd {a b} { + lassign $a _decimal sa ea ;#significand-a (aka mantissa-a), exponent-a + lassign $b _decimal sb eb + + if {$ea > $eb} { + set sa [expr {$sa * 10 ** ($ea-$eb)}] + set er $eb + } else { + set sb [expr {$sb * 10 ** ($eb-$ea)}] + set er $ea + } + set sr [expr {$sa + $sb}] + return [list decimal $sr $er] + } + + proc decpow2 {a b} { + #a**b + lassign $a _decimal sa ea + lassign $b _decimal sb eb + if {$sb eq 0} { + return [list decimal 1 0] + } else { + if {$sb < 0} { + #a**-n = 1/a**n + error "negative exponent not supported - sorry - try plain expr" + + } else { + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + + #powx = intermediate power x + + #sa **(sb * ten_2_eb) * 10**(ea*sb*(ten_2_eb)) + #sa **(sb * ten_2_eb) * 10**(exp1) + #sa **(sb * ten_2_eb) * pow2 + #pow3 * pow2 + + puts stdout "--->sa **(sb * ten_2_eb) * 10**(ea*sb*(ten_2_eb))<---" + puts stdout "--->sa **(sb * ten_2_eb) * 10**(exp1)<---" + + puts stdout "===>[subst {($sa ** ($sb * 10 **$eb)) * (10**($ea*$sb*(10**$eb))) }]<====" + set ten_2_eb [expr {10**$eb}] + puts stdout "->ten_2_eb: $ten_2_eb" + set exp1 [expr {$ea * $sb * $ten_2_eb}] + if {$exp1 < 0 } { + set exp1plus [string map {- ""} $exp1] + set d [expr {10**$exp1plus}] + puts stdout "...10**$exp1plus= $d" + set pow2 [divide_as_decimal 1 $d] + } else { + set pow2 [expr {10**$exp1}] + } + + puts stdout "->exp1: $exp1" + puts stdout "->pow2: $pow2" + + set exp2 [expr {$sb * $ten_2_eb}] + if {$exp2 < 0} { + set exp2plus [string map {- ""} $exp2] + set d [expr {$sa**$exp2plus}] + puts stdout "...$sa**$exp2plus= $d" + set pow3 [divide_as_decimal 1 $d] + } else { + set pow3 [expr {$sa ** $exp2}] + } + + set a1 [multiply_as_decimal $pow3 $pow2] + #return $a1 + return [str2dec $a1] + + set a1 [expr {($sa ** ($sb * 10 **$eb)) * (10**($ea*$sb*(10**$eb))) }] + return [str2dec $a1] + #set er [expr {$ea + $eb}] + + return [list decimal $sr $er] + } + } + } + + #broken + proc decpow {a b} { + puts stdout "decpow $a $b" + puts stderr "REVIEW - noted as 'broken' in source'" + #a**b + #make sure that all 'x ** y' operations within 'expr' involve integer x and positive integer y. + + lassign $a _decimal sa ea + lassign $b _decimal sb eb + if {$sb eq 0} { + return [list decimal 1 0] + } else { + if {$eb < 0} { + set ebplus [expr {abs($eb)}] + #set ten2eb [dec2str [decdiv {decimal 1 0} [str2dec [expr {10**$ebplus}]] ]] + #set x [expr {$sb * $ten2eb}] + set decpow [decpow {decimal 10 0} [list decimal $ebplus 0]] + + set divscale [expr {min(2,[string length [lindex $decpow 1]])}] + puts stdout "!!! decdiv {decimal 1 0} $decpow $divscale" + set ten2eb [decdiv {decimal 1 0} $decpow -dp $divscale] + set x [decmul [list decimal $sb 0] $ten2eb] + } elseif {$eb == 0} { + set x [list decimal $sb 0] + } else { + #set x [expr {$sb * (10 **$eb)}] + set x [decmul [list decimal $sb 0] [decpow {decimal 10 0} [list decimal $eb 0]]] + } + puts stdout "sb * (10 ** eb) -> $sb * (10 ** $eb) ----> x: $x" + + #raw eqn: + # sa*(10**ea) ** sb*(10**eb) + # using rule (xy)**n = (x**n)*(y**n) + # sa**(sb * 10**eb) * (10**ea)**(sb * 10**eb) + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #---------------------------------------------- + #sa**(bdec) * 10**(ea*bdec) + + #puts stdout "--->sa **(sb*(10**eb)) * 10**(ea * sb*(10**eb)))<---" + if {$ea == 0} { + puts stdout "------------ ea = 0" + #$a = $sa (since 10**0 = 1) + + #sa **(sb * (10**eb)) + #exponent law: x**mn = (x**m)**n + #(sa ** sb) ** (10**eb) + if {$eb == 0} { + puts stdout "------------ eb = 0 sa: $sa sb: $sb" + return [list decimal [expr {$sa ** $sb}] 0] + } elseif {$eb > 0} { + #sa ** (sb * (10**eb)) + puts stdout "------------ eb > 0" + #set rdec [decpow [str2dec [expr {$sa ** $sb}]] [list decimal 1 $eb]] + set r [expr ($sa ** $sb) ** (10**$eb)] + set a1 [str2dec $r] + return $a1 + + #puts stdout "xxxxxxx eb: $eb" + #return [list decimal [expr {$sa ** $sb}] [expr {10 ** $eb}]] + } else { + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #sa**(sb * 10**eb) * 10**0 + #---------------------------------------------- + puts stdout "------------ eb < 0" + #b = sb * (10** eb) + #if eb negative then + #b = sb * (1/(10**abs(eb))) + #b = sb / (10**abs(eb)) + #negative exponent of number b - therefore equivalent of 1/(sb**abs(eb)) + if {$sa < 0} { + error "negative values not handled here.. sorry" + } + #exponent law: x**(m/n) = nth root of x**m + + #sb * 1/10**abs(eb) + + #set 10eb [decpow {decimal 10 0} [list decimal [expr {abs($eb)}] 0]] + set 10eb [expr {10**abs($eb)}] + set b [decdiv [list decimal $sb 0] [list decimal $10eb 0]] + + puts "zzz" + return [decpow [list decimal $sa 0] $b] + + + + + #puts stdout "about to find '10**abs($eb)'th root of ($sa * (10**$sb))" + #set a1 [str2dec [root [expr {$sa * (10**$sb)}] [expr {10**abs($eb)}]]] + #return $a1 + + #puts stdout "about to root ($sa ** $sb) 10**[expr abs($eb)]" + #set a1 [str2dec [root [expr {$sa**$sb}] [expr {10**abs($eb)}]]] + #return $a1 + } + } else { + puts stdout "nonzero ea = $ea" + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #---------------------------------------------- + + #puts stdout "--->sa **(x) * 10**(ea * x)<---" + + #$b < 0 whenever $sb <0 + set xstr [dec2str $x] + if {$sb < 0} { + #raising a to a negative power + + #1/sa**abs($sb * 10**eb) + set xplus [expr {abs($xstr)}] + + puts stdout "a)-->sa ** xplus => $sa ** $xplus" + #set lhs [decdiv {decimal 1 0} [str2dec [expr {$sa ** $xplus}]]] + + #--- + #work out a scale + set p10a [root $sa 10] + set p10b [root $xplus 10] + set sc [multiply_as_decimal $p10a $p10b] + lassign [split $sc .] int frac + set sc [string length $int] + #--- + + set lhs [decdiv {decimal 1 0} [decpow [list decimal $sa 0] [list decimal $xplus 0]] -dp $sc] + + } else { + #> 0 (not = 0 because we've already handled that) + puts stdout "b)" + #set lhs [expr {$sa**$x}] + #set lhs [str2dec [expr {$sa ** $x}]] + if {[string first . $xstr] < 0} { + #puts stdout "-->str2dec $sa ** $xstr" + set lhs [str2dec [expr {$sa ** $xstr}]] ;#integers + } else { + set lhs [decpow [list decimal $sa 0] $x] + } + } + puts stdout "**lhs: $lhs" + + + #---------------------------------------------- + #sa**(sb * 10**eb) * 10**(ea*sb*(10**eb)) + #---------------------------------------------- + + if {[string first . $xstr] < 0} { + set exp2 [expr {$ea * $xstr}] ;#integers + } else { + set exp2 [dec2str [decmul [list decimal $ea 0] $x]] + } + #puts stdout " ea * x = $ea * $x = $exp2" + if {$exp2 < 0} { + puts stdout "c)" + set exp2plus [expr {abs($exp2)}] + #set d [expr {10**$exp2plus}] + #puts stdout "...10**$exp2plus= $d" + #set rhs [divide_as_decimal 1 $d] + if {$exp2plus > $::dollarcent::scale} { + set sc $exp2plus + } else { + set sc $::dollarcent::scale + } + + set rhs [decdiv {decimal 1 0} [str2dec [expr {10 ** $exp2plus}]] -dp $sc] + + + } else { + puts stdout "d) 10 ** $exp2" + #set rhs [expr {10 ** $exp2}] + set rhs [str2dec [expr {10 ** $exp2}]] + } + puts stdout "**rhs: $rhs" + + set a1 [decmul $lhs $rhs] + return $a1 + #set a1 [multiply_as_decimal $lhs $rhs] + #return [str2dec $a1] + } + } + } + + proc root {num {n 2} args} { + if {[string is integer -strict $n]} { + return [nthroot $num $n {*}$args] + } else { + return [expr {$num ** (1.0/$n)}] + } + } + + + #shifting nth root algorithm. Tested against 1st 50 or so places of root 2 - seems to work. + # - not practical for large values of n. + proc nthroot {num {n 2} args} { + set default [list -dp "" -scale $::dollarcent::scale ] + set opts [dict merge $default $args] + + lassign [split $num "."] int frac + + set dpdesired [dict get $opts -dp] + if {$dpdesired eq ""} { + set dpdesired [string length $frac] + } + + set dpworking [expr {max($dpdesired,[dict get $opts -scale])}] + + if {[string length $frac] < $dpworking} { + lappend frac [string repeat "0" [expr {$dpworking -[string length $frac]}]] + } + + set overlen [expr {[string length $int] % $n}] + if {$overlen > 0} { + set padding [string repeat 0 [expr {$n - $overlen}]] + } else { + set padding "" + } + set int $padding$int ;#padded on left with enough zeros so that we can grab groups of $n digits + + set digits [split $int {}] + + + set frac_digits [split $frac {}] + + set flen [llength $frac_digits] + if {$flen > 0} { + lappend frac_digits {*}[lrepeat [expr {$flen * ($n-1)}] 0] ;#we need another $n-1 digits for every existing digit in the fractional part + + #b + #set overlen [expr {[llength $frac_digits] % $n}] + #if {$overlen > 0} { + # lappend frac_digits {*}[lrepeat [expr {$n-$overlen}] 0] + #} + } + + lappend digits {*}$frac_digits + + + + #set overlen [expr {[string length $frac] % $n}] + #if {$overlen > 0} { + # set padding [string repeat 0 [expr $n - $overlen]] + # #append frac [string repeat "0" $flen] ;#???? + #} else { + # set padding "" + #} + #set frac $frac$padding + + #n = degree of root to be extracted = (10**eb) + #x = radicand processed thus far + #y = root extracted thus far + #r = remainder + #a = next n digits of the radicand + #B = next digit of the root + + #invariant y**n + r = x + #ie. y is the largest integer less than the nth root of x and r is the remainder. + + set x 0 + set y 0 + set r 0 + + #base = 10 - base of number system we're using. + #puts stdout "intfrac: '$int$frac'" + #puts stdout $digits + set nblock "" + foreach d $digits { + append nblock $d + if {[string length $nblock] < $n} { + continue + } + #puts stdout ". $nblock ." + scan $nblock %d a + set nblock "" ;#ready to build nex one. + + #set x2 [expr {(10**$n) * $x + $a}] ;#running power (radicand processed so far) + + #find largest integer B such that (10y + B)**n <= (10**n)x + a + #---- + # for square root case (n =2) + # 100y**2 + 20yB +B**2 <= 100x + a + #---- + #B will always be less than base ie B < 10 (see http://en.wikipedia.org/wiki/Shifting_nth_root_algorithm ) + for {set B 0} {$B < 10} {incr B} { + if {(((10 * $y + $B) ** $n) - (10**$n * $y**$n)) <= (((10**$n) * $r) + $a)} { + #ok + } else { + #overshot + break + } + } + incr B -1 + set y2 [expr {(10 * $y) + $B}] + set r2 [expr {(10**$n)*$r + $a - ((10*$y + $B)**$n - (10**$n)*($y**$n))}] + + set y $y2 + set r $r2 + + #puts -nonewline $B + } + #puts stdout "" + #puts stdout "y: $y" + + #set a [expr {$y / pow(10,([llength $frac_digits]/$n))}] + #puts stdout "answer1: $a" + + #set sc [expr {[llength $frac_digits] / $n}] + #set sc $::dollarcent::scale + set sc $dpdesired + + set y [dec2str [decdiv [str2dec $y] [str2dec [expr {10**([llength $frac_digits]/$n)}] ] -dp $sc] ] + return $y + } + + + #decimal product + proc decmul {a b} { + #puts stdout "decmul $a $b" + lassign $a _decimal sa ea ;#significand-a exponent-a + lassign $b _decimal sb eb + + #integer operations - not floating point. + set sr [expr {$sa * $sb}] + set er [expr {$ea + $eb}] + + return [list decimal $sr $er] + } + + #decimal division + #rounds last digit. (automatically chooses scale large enough to provide additional 2 digits to determine rounding value) + proc decdiv {a b args} { + variable scale + set default [list -dp ""] + set opts [dict merge $default $args] + set dpdesired [dict get $opts -dp] + + if {$dpdesired ne ""} { + #set sc $dpdesired + set dp_exact 1 + } else { + set dpdesired $scale + set dp_exact 0 ;#we're allowed to trim repeat rhs zeros + } + if {$dpdesired >= $scale} { + set sc [expr {$dpdesired + 2}] ;#2 additional digits required for rounding + } else { + if {($scale - $dpdesired) == 1} { + set sc [expr {$dpdesired + 2}] ;#scale is just above dpdesired + } else { + set sc $scale + } + } + #set sc [expr {max($dpdesired,$scale)}] + + lassign $a _decimal sa ea ;#sa significand-a, ea exponent-a + lassign $b _decimal sb eb + + if {$ea >=0} { + set dpa 0 + } else { + set dpa [expr {abs($ea)}] + } + if {$eb >=0} { + set dpb 0 + } else { + set dpb [expr {abs($eb)}] + } + + + set ab_highest_dp [expr {max($dpa,$dpb)}] + set ab_lowest_dp [expr {min($dpa,$dpb)}] + set ab_diff_dp [expr {$dpb - $dpa}] + + set dpr [expr {$sc - $ab_diff_dp}] ;#decimal places in the result. + #puts stdout "sc:$sc - (dpb:$dpb - dpa:$dpa) = $sc - [expr {$dpb - $dpa}]. dp result: $dpr" + + set desired_min_dp [expr {min($dpdesired,$ab_highest_dp)}] + + #sc must not be < $dpa, or the calculation of sr1 below will not be a pure integer operation! + + #!todo - sc should be large enough that dpdesired+2 dp of digits always available for accurate/consistent rounding. + # (so we can differentiate betw .50 and .5x when using a rounding method for which this makes a difference) + + if {$dpr != ($dpdesired + 2)} { + incr sc [expr {max((-$sc + $dpa),$ab_diff_dp)}] + set dpr [expr {$sc - $ab_diff_dp}] ;#decimal places in the result. + + if {$dpr < $desired_min_dp} { + incr sc [expr {$desired_min_dp - $dpr}] + set dpr [expr {$sc - $ab_diff_dp}] ;#decimal places in the result. + } + } + #puts stdout "sc:$sc - ($dpb - $dpa) = $sc - [expr {$dpb - $dpa}]. dp result: $dpr" + + #puts stdout "calc: $sa * (10**$sc) / $sb" + set sr1 [expr {$sa * (10**$sc) / $sb}] ;#integer maths. ($sc always positive) Result will not be in scientific notation, and will not have a radix point. + if {$sr1 eq 0} { + set sr 0 + set er 0 + } else { + #jmn + #round if dpdesired < dpr + #(this result may be used as an intermediate for other calcs - not appropriate to aggressively round or limit sigfigs unless explicitly set using dpdesired) + if {$dpdesired >= $dpr} { + set sr $sr1 + set er [expr {$ea - $eb - $sc}] + } else { + set excess [expr {$dpr - $dpdesired}] + set sr [string range $sr1 0 end-$excess] + + set tail [string range $sr1 end-[expr {$excess -1}] end-[expr {$excess -2}]] + lassign [split $tail {}] e1 e2 + #puts stdout "dpr: $dpr len sr1:[string length $sr1] sr1: $sr1 sr: $sr tail: $tail" + + if {$e1 >= 6} { + incr sr + } elseif {$e1 == 5} { + if {$e2 == 0} { + #round based on current rounding method. + set sr [do_round $sr $e1 $e2] + + #round half up + #incr sr + } else { + #>50 always round up. + incr sr + } + } + set er [expr {$ea - $eb - $sc +$excess}] + } + } + return [list decimal $sr $er] + } + + + #-------------------------------------------- + #!todo - something. + # - what about countries which use USD or AUD etc instead of having their own currency? + variable currency_symbol + variable currency_country + + #e.g http://coinmill.com/sources.html + #we use smallestmajor for their term "Smallest Currency Unit" + #This is potentially useful to implement 'cash' rounding + #- but it should be noted that this is changeable as countries withdraw their smallest units again and again whilst they inflate away savings. + + dict set currency_symbol AUD [list domain Australia majorunit dollar minorunit cent smallestmajor 0.05 symbol AUD] + dict set currency_symbol AFN [list domain Afghanistan majorunit afghani minorunit "" smallestmajor 1 symbol AFN] + dict set currency_symbol EUR [list domain "European Union" majorunit euro minorunit cent smallestmajor 0.01 symbol EUR] + dict set currency_symbol GBP [list domain "UK" majorunit pound minorunit cent smallestmajor 0.01 symbol GBP] + dict set currency_symbol NZD [list domain "New Zealand" majorunit dollar minorunit cent smallestmajor 0.10 symbol NZD] + dict set currency_symbol USD [list domain "United States" majorunit dollar minorunit cent smallestmajor 0.01 symbol USD] + dict set currency_symbol {L$} [list domain "Linden" majorunit dollar minorunit cent smallestmajor 0.01 symbol {L$} note "Second Life"] + + #For cryptocurrencies - smallestmajor could be considered to be the smallest divisibility - but there are often network limits + #on what is in practice the smallest transferable amount. (e.g transaction fees, variable based on network capacity/demand and relative value of the currency) + dict set currency_symbol BTC [list domain "Bitcoin" majorunit BTC minorunit satoshi smallestmajor 0.00000001 symbol "\U20BF"] + dict set currency_symbol BCH [list domain "Bitcoin Cash" majorunit BCH minorunit satoshi smallestmajor 0.00000001 symbol "BCH"] + #eth smallest major 1e-18 'wei' - but 'gwei' 1e-9 is commonly uses as 'Ethereum gas' is paid in gwei + dict set currency_symbol ETH [list domain "Ethereum" majorunit ETH minorunit wei smallestmajor 0.000000000000000001 symbol "ETH"] + #-------------------------------------------- + + + + + #shortcut methods - from dollars to various fractional-cent units. + #interp alias {} ${ns}::d2c {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-0 {*}$args}}] + interp alias {} ${ns}::d2c {} ::dollarcent::convert d to c-0 + #interp alias {} ${ns}::d2xc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-1 {*}$args}}] + interp alias {} ${ns}::d2xc {} ::dollarcent::convert d to c-1 + #interp alias {} ${ns}::d2cc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-2 {*}$args}}] + interp alias {} ${ns}::d2cc {} ::dollarcent::convert d to c-2 + #interp alias {} ${ns}::d2mc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-3 {*}$args}}] + interp alias {} ${ns}::d2mc {} ::dollarcent::convert d to c-3 + #interp alias {} ${ns}::d2_xc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-4 {*}$args}}] + interp alias {} ${ns}::d2_xc {} ::dollarcent::convert d to c-4 + #interp alias {} ${ns}::d2_cc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-5 {*}$args}}] + interp alias {} ${ns}::d2_cc {} ::dollarcent::convert d to c-5 + #interp alias {} ${ns}::d2_mc {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt d to c-6 {*}$args}}] + interp alias {} ${ns}::d2_mc {} ::dollarcent::convert do to c-6 + namespace export d2c d2xc d2cc d2mc d2_xc d2_cc d2_mc + + #shortcut methods - from various fractional-cent units to dollars. + #interp alias {} ${ns}::c2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-0 to d {*}$args}}] + interp alias {} ${ns}::c2d {} ::dollarcent::convert c-0 to d + #interp alias {} ${ns}::xc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-1 to d {*}$args}}] + interp alias {} ${ns}::xc2d {} ::dollarcent::convert c-1 to d + #interp alias {} ${ns}::cc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-2 to d {*}$args}}] + interp alias {} ${ns}::cc2d {} ::dollarcent::convert c-2 to d + #interp alias {} ${ns}::mc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-3 to d {*}$args}}] + interp alias {} ${ns}::mc2d {} ::dollarcent::convert c-3 to d + #interp alias {} ${ns}::_xc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-4 to d {*}$args}}] + interp alias {} ${ns}::_xc2d {} ::dollarcent::convert c-4 to d + #interp alias {} ${ns}::_cc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-5 to d {*}$args}}] + interp alias {} ${ns}::_cc2d {} ::dollarcent::convert c-5 to d + #interp alias {} ${ns}::_mc2d {} apply [string map [list %ns% $ns] {{amt args} {%ns%::convert $amt c-6 to d {*}$args}}] + interp alias {} ${ns}::_mc2d {} ::dollarcent::convert c-6 to d + namespace export c2d xc2d cc2d mc2d _xc2d _cc2d _mc2d + namespace export convert roundcents + + #namespace export root + #namespace export decadd decsub decmul decdiv decpow str2dec dec2str sigfigs + #namespace export roundcents multiply_as_decimal divide_as_decimal add_as_decimal sub_as_decimal power_as_decimal + + + + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace dollarcent ---}] +} + +#+ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval dollarcent::lib { + namespace export {[a-z]*}; # Convention: export all lowercase + namespace path [namespace parent] + #*** !doctools + #[subsection {Namespace dollarcent::lib}] + #[para] Secondary functions that are part of the API + #[list_begin definitions] + + #proc utility1 {p1 args} { + # #*** !doctools + # #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] + # #[para]Description of utility1 + # return 1 + #} + + + + #*** !doctools + #[list_end] [comment {--- end definitions namespace dollarcent::lib ---}] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +#*** !doctools +#[section Internal] +namespace eval dollarcent::system { + #*** !doctools + #[subsection {Namespace dollarcent::system}] + #[para] Internal functions that are not part of the API + + + +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide dollarcent [namespace eval dollarcent { + variable pkg dollarcent + variable version + set version 1.1 +}] +return + +#*** !doctools +#[manpage_end] + diff --git a/src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/project-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/project-0.1.0.tm index 4f108187..8384197a 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/project-0.1.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/project-0.1.0.tm @@ -433,20 +433,26 @@ namespace eval punk::mix::commandset::project { #scan all files in template # #TODO - deck command to substitute templates? - set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] + set templateinfo_list [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] set stripprefix [file normalize $layout_path] set tagmap [list [lib::template_tag project] $projectname] - if {[llength $templatefiles]} { + if {[llength $templateinfo_list]} { puts stdout "Filling template file placeholders with the following tag map:" foreach {placeholder value} $tagmap { puts stdout " $placeholder -> $value" } } - foreach templatefullpath $templatefiles { + foreach templateinfo $templateinfo_list { + lassign $templateinfo templatefullpath template_tagnames_found set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] - set fpath [file join $projectdir $templatetail] + foreach t $template_tagnames_found { + if {"%$t%" ni [dict keys $tagmap]} { + puts stderr "warning: No substitution available for tag: %$t% in $fpath" + } + } + if {[file exists $fpath]} { set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd set data2 [string map $tagmap $data] @@ -458,7 +464,6 @@ namespace eval punk::mix::commandset::project { puts stderr "warning: Missing template file $fpath" } } - #todo - tag substitutions in src/doc tree ::cd $projectdir