Add scale to Number formatter
* src/validate/formatter/Number.js (__mixin): Add mixin ctor. (parse): Handle scale. (styleNumber): Handle scale. (scale, split): Add methods. * test/validate/formatter/NumberTest.js: Modified accordingly.master
parent
6db99c8632
commit
5947e7646e
|
@ -1,4 +1,5 @@
|
|||
/**
|
||||
* @license
|
||||
* Number formatter
|
||||
*
|
||||
* Copyright (C) 2016 LoVullo Associates, Inc.
|
||||
|
@ -25,11 +26,83 @@ var Trait = require( 'easejs' ).Trait,
|
|||
|
||||
/**
|
||||
* Formats numbers in en_US locale
|
||||
*
|
||||
* Only whole numbers are permitted by default unless the mixin is
|
||||
* initialized with a scale, where the scale represents the number of
|
||||
* digits of the significand. If the scale is positive, the
|
||||
* significand will be padded with zeroes to meet the scale; if
|
||||
* negative, trailing zeroes will be removed. The significand will be
|
||||
* truncated (not rounded) if it exceeds the scale:
|
||||
*
|
||||
* @example
|
||||
* EchoFormatter.use( Number ).parse( '00003' ) // => 3
|
||||
* EchoFormatter.use( Number( -6 ) ).parse( '3.14159' ) // => 3.14159
|
||||
* EchoFormatter.use( Number( 6 ) ).parse( '3.14159' ) // => 3.141590
|
||||
* EchoFormatter.use( Number( 2 ) ).parse( '3.14159' ) // => 3.14
|
||||
*
|
||||
* Leading zeroes are stripped.
|
||||
*/
|
||||
module.exports = Trait( 'Number' )
|
||||
.implement( ValidatorFormatter )
|
||||
.extend(
|
||||
{
|
||||
/**
|
||||
* Number of digits after the decimal point
|
||||
*
|
||||
* This value should never be negative.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
'private _scale': 0,
|
||||
|
||||
/**
|
||||
* Pre-computed zero-padding of scale
|
||||
*
|
||||
* This conveniently allows prefixing this padding with a number
|
||||
* and then truncating to scale.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
'private _scalestr': '',
|
||||
|
||||
|
||||
/**
|
||||
* Initialize optional scale
|
||||
*
|
||||
* The scale SCALE is an optional value that specifies the number
|
||||
* of digits after the decimal point to display. Note that
|
||||
* trailing zeros are _not_ removed, making this ideal for
|
||||
* i.e. currency.
|
||||
*
|
||||
* The "scale" terminology comes from the Unix bc tool. If
|
||||
* positive, the significand will be padded with zeros to meet the
|
||||
* scale. If negative, no padding will take place.
|
||||
*
|
||||
* If the significand has more digits than permitted by SCALE, it
|
||||
* is truncated.
|
||||
*
|
||||
* @param {number} scale number of digits after decimal point
|
||||
*/
|
||||
__mixin: function( scale )
|
||||
{
|
||||
this._scale = Math.abs( scale ) || 0;
|
||||
this._scalestr = this._padScale( +scale );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Create scale padding for significand
|
||||
*
|
||||
* @return {string} string with SCALE zeroes
|
||||
*/
|
||||
'private _padScale': function( scale )
|
||||
{
|
||||
return ( scale > 0 )
|
||||
? ( new Array( this._scale + 1 ) ).join( '0' )
|
||||
: '';
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Parse item as a number
|
||||
*
|
||||
|
@ -39,7 +112,10 @@ module.exports = Trait( 'Number' )
|
|||
*/
|
||||
'virtual abstract override public parse': function( data )
|
||||
{
|
||||
return this.__super( data ).replace( /[ ,]/g, '' );
|
||||
var cleaned = this.__super( data ).replace( /[ ,]/g, '' ),
|
||||
parts = this.split( cleaned );
|
||||
|
||||
return parts.integer + this.scale( parts.significand, this._scale );
|
||||
},
|
||||
|
||||
|
||||
|
@ -65,7 +141,9 @@ module.exports = Trait( 'Number' )
|
|||
*/
|
||||
'virtual protected styleNumber': function( number )
|
||||
{
|
||||
var i = number.length,
|
||||
var parts = this.split( number );
|
||||
|
||||
var i = parts.integer.length,
|
||||
ret = [],
|
||||
chunk = '';
|
||||
|
||||
|
@ -83,6 +161,56 @@ module.exports = Trait( 'Number' )
|
|||
ret.unshift( chunk );
|
||||
} while ( i > 0 );
|
||||
|
||||
return ret.join( ',' );
|
||||
return ret.join( ',' ) +
|
||||
this.scale( parts.significand, this._scale );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Parse significand and return scaled value
|
||||
*
|
||||
* If the result is non-empty, then the result will be prefixed
|
||||
* with a decimal point.
|
||||
*
|
||||
* Truncation is determined by whether the initial scale was
|
||||
* negative. If so, this method will lack a zero padding string
|
||||
* and return a result without trailing zeroes.
|
||||
*
|
||||
* @param {string} significand value after decimal point
|
||||
* @param {number} scale positive scale
|
||||
*
|
||||
* @return {string} scaled significand with decimal point as needed
|
||||
*/
|
||||
'virtual protected scale': function( significand, scale )
|
||||
{
|
||||
if ( scale <= 0 )
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// easy cheat: use the pre-filled scale and truncate
|
||||
var result = ( significand + this._scalestr ).substr( 0, scale );
|
||||
|
||||
return ( result )
|
||||
? '.' + result
|
||||
: '';
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Split integer from significand in NUMBER
|
||||
*
|
||||
* @param {string} number number to split
|
||||
*
|
||||
* @return {Object.<integer,decimal>} integer and significand
|
||||
*/
|
||||
'virtual protected split': function( number )
|
||||
{
|
||||
var parts = number.split( '.' );
|
||||
|
||||
return {
|
||||
integer: parts[ 0 ].replace( /^0+/, '' ) || '0',
|
||||
significand: ( parts[ 1 ] || '' ).replace( /0+$/, '' ),
|
||||
}
|
||||
}
|
||||
} );
|
|
@ -27,8 +27,10 @@ var liza = require( '../../../' ),
|
|||
|
||||
describe( 'validate.formatter.Number', function()
|
||||
{
|
||||
// default case, no decimal places
|
||||
common.testValidate( EchoFormatter.use( Sut )(), {
|
||||
"1": [ "1", "1" ],
|
||||
"001": [ "1", "1" ],
|
||||
"123": [ "123", "123" ],
|
||||
"12345": [ "12345", "12,345" ],
|
||||
"12,345": [ "12345", "12,345" ],
|
||||
|
@ -36,6 +38,47 @@ describe( 'validate.formatter.Number', function()
|
|||
"12,345,": [ "12345", "12,345" ],
|
||||
" 12,345 ,": [ "12345", "12,345" ],
|
||||
" 1, ,": [ "1", "1" ],
|
||||
|
||||
// strip decimals
|
||||
"1.234": [ "1", "1" ],
|
||||
" 1, ,.": [ "1", "1" ],
|
||||
} );
|
||||
|
||||
|
||||
// decimal places
|
||||
common.testValidate( EchoFormatter.use( Sut( 3 ) )(), {
|
||||
"1": [ "1.000", "1.000" ],
|
||||
"001": [ "1.000", "1.000" ],
|
||||
"123": [ "123.000", "123.000" ],
|
||||
"123.1": [ "123.100", "123.100" ],
|
||||
"0123.1": [ "123.100", "123.100" ],
|
||||
"123.155": [ "123.155", "123.155" ],
|
||||
"123.": [ "123.000", "123.000" ],
|
||||
".123": [ "0.123", "0.123" ],
|
||||
|
||||
// truncate, not round (leave that to another formatter)
|
||||
"123.1554": [ "123.155", "123.155" ],
|
||||
"123.1556": [ "123.155", "123.155" ],
|
||||
|
||||
"12,345": [ "12345.000", "12,345.000" ],
|
||||
" 1, ,.": [ "1.000", "1.000" ],
|
||||
} );
|
||||
|
||||
|
||||
// really long decimals should be unstyled
|
||||
common.testValidate( EchoFormatter.use( Sut( 10 ) )(), {
|
||||
"0.1234567890": [ true, true ],
|
||||
} );
|
||||
|
||||
|
||||
// negative scale strips trailing zeroes
|
||||
common.testValidate( EchoFormatter.use( Sut( -5 ) )(), {
|
||||
"1": [ "1", "1" ],
|
||||
"01": [ "1", "1" ],
|
||||
"1.0": [ "1", "1" ],
|
||||
"1.0100": [ "1.01", "1.01" ],
|
||||
"123.155": [ "123.155", "123.155" ],
|
||||
"123.123456": [ "123.12345", "123.12345" ],
|
||||
} );
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue