Difference between revisions of "SHIP:Sail:towards"

From Serious Documentation
Jump to: navigation, search
(Created page with "__NOTOC__ {{SailFuncTableStart|}}<onlyinclude> |{{SailFunc|bytesToString}}||{{DataType|String}}||style="text-align:center;"|v5.0.207||Builds a string from the ASCII bytes of a...")
 
 
(30 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
__NOTOC__
 
__NOTOC__
 
{{SailFuncTableStart|}}<onlyinclude>
 
{{SailFuncTableStart|}}<onlyinclude>
|{{SailFunc|bytesToString}}||{{DataType|String}}||style="text-align:center;"|v5.0.207||Builds a string from the ASCII bytes of an  {{DataType|Integer}}, {{DataType|Short}}, or {{DataType|Byte}} until a <code>0x00</code> byte is encountered or the number of bytes of the size of the object are consumed. Optionally, non-printable characters can be replaced as-encountered. All byte orderings are supported.</onlyinclude>
+
|{{SailFunc|towards}}||{{DataType|Float}}/{{DataType|Integer}}||style="text-align:center;"|v5.0.207||Adjusts a number by a percentage in a range towards another number</onlyinclude>
 
|}
 
|}
 
== See Also: ==
 
== See Also: ==
 
*[[SHIP:Sail|Sail Home]]
 
*[[SHIP:Sail|Sail Home]]
 
*[[SHIP:Sail:Functions|Sail Functions]]
 
*[[SHIP:Sail:Functions|Sail Functions]]
*[[SHIP:Sail:String Functions|Sail String Functions]]
+
*[[SHIP:Sail:Numeric Functions|Sail Numeric Functions]]
*{{SailFunc|charAt}}, {{SailFunc|codepointAt}}, {{SailFunc|codepointToString}},{{SailFunc|subStr}}, {{SailFunc|subString}}, {{SailFunc|toString}}
+
*{{SailFunc|mapRange}}
 
== Prototypes ==
 
== Prototypes ==
<code>{{DataType|String}} {{SailFunc|bytesToString}}({{DataType|Float}}/{{DataType|Integer}}/{{DataType|Short}}/{{DataType|Byte}} number, {{DataType|Byte}} nBytes);</code>
+
<code>{{DataType|Float}} {{SailFunc|towards}}({{DataType|Float}} percent, {{DataType|Float}} current, {{DataType|Float}} target[, {{DataType|Float}} snapWithin ]);</code>
 
 
<code>{{DataType|String}} {{SailFunc|bytesToString}}({{DataType|Float}}/{{DataType|Integer}}/{{DataType|Short}}/{{DataType|Byte}} number, {{DataType|Byte}} nBytes, {{DataType|ByteOrder}} byteOrder);</code>
 
 
 
<code>{{DataType|String}} {{SailFunc|bytesToString}}({{DataType|Float}}/{{DataType|Integer}}/{{DataType|Short}}/{{DataType|Byte}} number, {{DataType|Byte}} nBytes, {{DataType|ByteOrder}} byteOrder, {{DataType|Codepoint}} replacement );</code>
 
  
 +
<code>{{DataType|Integer}} {{SailFunc|towards}}({{DataType|Float}} percent, {{DataType|Integer}} current, {{DataType|Integer}} target[, {{DataType|Integer}} snapWithin ]);</code>
 
=== Parameters/Return Value ===
 
=== Parameters/Return Value ===
 
{| class="wikitable" style="margin: 1em 1em;"  
 
{| class="wikitable" style="margin: 1em 1em;"  
Line 21: Line 18:
 
! scope="col" style="text-align:left" | Description
 
! scope="col" style="text-align:left" | Description
 
|-
 
|-
|number||{{DataType|Float}}<br/>{{DataType|Integer}}<br/>{{DataType|Short}}<br/>{{DataType|Byte}}||Number to convert.<br/>The size of the source type drives the maximum number of bytes to convert; constants are assumed to have the least number of representation bytes.
+
|percent||{{DataType|Float}}||Percent to move ''current'' towards ''target''
 
|-
 
|-
|nBytes||{{DataType|Byte}}||Maximum number of bytes to process in the object. All values are promoted to signed {{DataType|Integer}} at runtime, but on a maximum of nBytes are processed in the byteOrder specified (or {{Reserved|BYTEORDER.BIG_ENDIAN}} if unspecified).
+
|current||{{DataType|Float}}<br/>{{DataType|Integer}}||Current value
 
|-
 
|-
|byteOrder||{{DataType|ByteOrder}}||Byte order to analyze the source number with; default is {{Reserved|BYTEORDER.BIG_ENDIAN}} (optional)
+
|target||{{DataType|Float}}<br/>{{DataType|Integer}}||Target ending value
 
|-
 
|-
|replacement||{{DataType|Codepoint}}||If a non-printable character (<code>0x01-0x1F</code>) is encountered, this UTF8 {{DataType|Codepoint}} is inserted into the string instead. <code>0x00</code> is permitted here and terminates the string upon encountering a non-printable byte. Any Unicode {{DataType|Codepoint}} is permitted.
+
|snapWithin ||{{DataType|Float}}<br/>{{DataType|Integer}}||(optional) When the result would be less than or equal to this distance from the target, the value is "snapped" to the target and the target value is returned. Defaults to 1 (or 1.0f) if not supplied.
 
|-
 
|-
 
! scope="col" style="text-align:left" | Return
 
! scope="col" style="text-align:left" | Return
! scope="col" style="text-align:left" | {{DataType|String}}
+
! scope="col" style="text-align:left" | {{DataType|Float}}<br/>{{DataType|Integer}}
! scope="col" style="text-align:left" | Character string
+
! scope="col" style="text-align:left" | A new number "towards" the target
 
|}
 
|}
  
 
== Detailed Description ==
 
== Detailed Description ==
The {{SailFunc|bytesToString}}<code>()</code> function returns the character string representing the 8, 16, or 32-bit ASCII byte sequence held in the number specified.  
+
The {{SailFunc|towards}} function moves a current numeric value ''towards'' a target value by a percentage.
 +
 
 +
The function is very useful in moving objects (e.g. gauge needles, sliders, boxes/images) in a fluid manner from their current positions to a target position. For example, when a slider needs to move from pixel position 55 to 85, rather than just assign the slider's position directly to 85 on a change, one can create a much more fluid movement using {{SailFunc|towards}}. 
 +
 
 +
The structure of this would use a timer within the object (such as a slider box):
 +
 
 +
<code>
 +
    variable {{DataType|Integer}} target
 +
    box
 +
        timer oneshot=true value=1 period=1 autoreload=true enabled=false
 +
            listener listeningto=timer.alarm condition=timer.alarm
 +
                script
 +
                    box.ol        = towards(0.33f, box.ol, target);
 +
                    timer.alarm  = false;
 +
                    timer.enabled = (target != box.ol);
 +
            listener listeningto=target
 +
                    timer.enabled = (target != box.ol);
 +
</code>
 +
 
 +
=== The ''snapWithin'' Parameter and the {{DataType|Float}} Variant of the Function ===
 +
In the floating point version of this function (where any of the 3 parameters ''current'', ''target'', and/or ''snapWithin'' are supplied as floating point values), the returned value will asymptotically approach the target.
 +
 
 +
By specifying the ''snapWithin'' parameter, when the returned value would be less than or equal to a ''snapWithin'' distance from the target, the target value is returned.
  
For example, the 32-bit integer <code>0x53484950</code> is a big endian order representation of "SHIP".
+
For example, continuously moving 25% towards a target number using floating point values:
  
The most common use of this function is when receiving Modbus or other communications variables from a remote system that are encoding strings or character sequences. For example, the remote end may pass {{DataType|Short}} variables with 2-letter codes describing the state of a machine; say <code>0x444F</code> or "<code>DO</code>" for Door Open. You may want to display these codes in your GUI, and so converting them to a {{DataType|String}} is desirable. One option in this example is
+
<code>
 +
    Float f = 1.0f;
 +
    ...
 +
    (repeated) f = {{SailFunc|towards}}(0.25f, f, 5.0f, 0.1f);
 +
</code>
  
<code>{{DataType|String}} = {{SailFunc|codepointToString}}((x>>8)&0x0FF) + {{SailFunc|codepointToString}}(x&0x0FF);</code>
+
Will, upon repetition of the formula, adjust f by 25% towards the target of 5.0 until is within 0.1 of 5.0, at which point 5.0 will be returned.
  
But this technique gets cumbersome with {{DataType|Integer}} variables.  Further, what if the encoding in the number has non-printable characters you wish to replace, and/or you may encounter a string-ending <code>0x00</code> byte in the sequence?  The method above does not handle these cases.
+
''snapWithin'', if not supplied, defaults to 1.0f in the floating point variant of the function.
  
The {{SailFunc|bytesToString}}<code>()</code> function makes parsing these objects much simpler.
+
=== The ''snapWithin'' Parameter and the {{DataType|Integer}} Variant of the Function ===
=== Size of Source Object ===
 
Numeric operands in SHIP are always promoted to 32-bit signed numbers during mathematical manipulation and assignment. Since all number types in SHIP are signed, all 32-bit values used in math are signed {{DataType|Integer}} values.  When a smaller {{Node|variable}} or {{Node|const}} objects (i.e. ({{DataType|Byte}} or {{DataType|Short}}) are supplied in expressions, they will be sign extended to the full 32-bits. For example, a {{Node|const}} such as <code>0x99</code> will always be sign extended to <code>0xFFFFFF99</code> during use.
 
  
With the {{SailFunc|bytesToString}} function, this can cause some interesting side effects. Consider {{SailFunc|bytesToString}}<code>(0x81)</code> which would, ideally, should return "&#153;". However, because of sign extension and 32-bit promotion, the <code>0x99</code> will be promoted to <code>0xFFFFFF99</code> and the full return from the {{SailFunc|bytesToString}}<code>(0x99)</code> would be "&#255;&#255;&#255;&#153;". This is rarely the desirable outcome.
+
When using integer values, an infinite loop possibility exists where the current never (after repeated assigns) reaches the target.
  
Therefore there is an additional mandatory parameter, <code>nBytes</code>, which helps address this issue. Note the byte ordering (see below) is performed in advance of the characters being processed.
+
For example, 33% of the distance between 0 and 1 is always 0 from an integer perspective, and so repeated calls with these parameters will never have the current reach the target.
  
You still need to take care that you're processing the bytes you truly desire. For example, if a single byte is desired to be processed from the lowest byte position of a 32-bit value, using {{SailFunc|bytesToString}}<code>(0x99,1)</code> will not generate the expected "&#153;" because the default byte ordering is big Endian, and the first byte processed (of the 1 allowed by the <code>nBytes</code> parameter) is a sign-extended <code>0xFF</code>.  To get the correct result, one should specify in this case {{SailFunc|bytesToString}}<code>(0x99,1,</code>{{Reserved|BYTEORDER.LITTLE_ENDIAN}}<code>);</code>.
+
In a related example, 25% of the distance between 0 and 2 is always 0 from an integer perspective, again causing a potential infinite loop where the current value never moves at all.
  
=== Byte Ordering ===
+
There are 2 mechanisms that prevent these behaviors.
Often the bytes in the number object are not in the same order. Often communications {{DataType|Integer}} values come in big Endian, but other times you may have all sorts of needs for the 4 different byte orderings of a 32-bit value (or 2 for a 16-bit value).
 
  
The optional parameter allows you to manipulate the byte ordering that the object is processed in. The {{DataType|ByteOrder}} value is similar to that supported in {{Node|linkset}} nodes to convert incoming data, and a typical value might be {{Reserved|BYTEORDER.SWAP8}} to swap pairs of 8-bit values in a 16- or 32-bit number.
+
First, the remaining distance to the target is internally calculated and truncated.  Therefore any fractional amount will cause the current to move by at least 1. This mechanism handles both problematic scenarios above.
 +
 
 +
The ''snapWithin'', if not supplied, defaults to 1 in the integer variant of the function, and is redundant with the remaining-distance truncation method. However, a designer may choose a greater value to "snap to" a target when within a larger distance.
 +
=== Troubleshooting and Common Issues ===
 +
The most common problem with {{SailFunc|towards}} occurs when inadvertently you are using the floating point variant of the function with an integer variable as the result.  In this case, it is easy to have the function get "stuck" and never reach the target.  For example, repeated calls to
 +
<code>
 +
current = towards(0.33f, current, target);
 +
</code>
 +
if ''current'' and ''target'' are both integers ({{DataType|Integer}}, {{DataType|Short}}, and/or {{DataType|Byte}}) will converge as expected to the final condition when ''current'' is equal to ''target''.  This is because the distance remaining between the two values at each iteration is truncated down, and so the value of current always moves at least 1 count every iteration.
 +
 
 +
However, if inadvertently ''target'' is specified as a floating point number, for example if ''current'' is 82 and ''target'' is 85.0f, the function is promoted to the floating point variant, and the result of the function is now <code>82.0+(1-0.33)*(85.0-82.0) = 82.99</code> which, on assignment to ''current'' is truncated to 82.  Hence the function never converges to the ''target'' as expected.
 +
 
 +
The simple solution to this issue, in this example, is to force the rounding or truncation of the ''target'' value as desired into an integer form, making the function use the integer variant:
 +
<code>
 +
current = towards(0.33f, current, round(target));
 +
</code>
 +
 
 +
Be careful with attempting this form of solution for this issue:
 +
<code>
 +
current = round(towards(0.1f, 82, 85.0f));
 +
</code>
 +
as you will find cases (as in this exact example) where the {{SailFunc|round}} is ineffectual -- <code>82.0+(1-0.1)*(85.0-82.0) = 82.3</code> which still rounds down to 82... hence still non-converging.
  
 
== Examples ==
 
== Examples ==
Line 66: Line 107:
 
! scope="col" style="text-align:left" | Notes
 
! scope="col" style="text-align:left" | Notes
 
|-
 
|-
|<code>bytesToString(0x53,1);</code> || "" || The bytes are sign extended to <code>0x00000053</code>, and processed by default in big Endian order, so the first byte encountered is a <code>0x00</code> which ends the string.
+
|<code>towards(0.25f,0,4);</code> || 1 || 25% from 0 to 4 is 1.
 
|-
 
|-
|<code>bytesToString(0x53,1,</code>{{Reserved|BYTEORDER.LITTLE_ENDIAN}}<code>);</code> || "S" ||
+
|<code>towards(0.25f,4,0);</code> || 3 || 25% from 4 to 0 is 3.
 
|-
 
|-
|<code>bytesToString(0x53484950,4);</code> || "SHIP" ||  
+
|<code>towards(0.25f,-4,4);</code> || -2 || 25% from -4 to +4 is 2.
 
|-
 
|-
|<code>bytesToString(0x53484900,4);</code> || "SHI" ||  
+
|<code>towards(0.25f,3,4);</code> || 4 || Integer form of function, remaining distance (0.75) is truncated so the return value is 4.
 
|-
 
|-
|<code>bytesToString(0x53480050,4);</code> || "SH" || Early terminated when <code>0x00</code> encountered in 3rd byte.
+
|<code>towards(0.25f,3,4.0f,0.25f);</code> || 3.25f || Floating point form of function
 
|-
 
|-
|<code>bytesToString(0x53484950,4,</code>{{Reserved|BYTEORDER.LITTLE_ENDIAN}}<code>);</code> || "PIHS" || All 4 bytes endian flipped.
+
|<code>towards(0.75f,3,4.0f,0.25f);</code> || 4.00f || Floating point form of function; result is 3.75 but is within the 0.25f snapWithin range so result is target.
|-
 
|<code>bytesToString(0x53484950,4,</code>{{Reserved|BYTEORDER.BIG_ENDIAN}}<code>,0x3F);</code> || "SHIP" || All bytes are printable, so the replacement character <code>3F</code> ('?') is unused.
 
|-
 
|<code>bytesToString(0x53480350,4,</code>{{Reserved|BYTEORDER.BIG_ENDIAN}}<code>,0x3F);</code> || "SH?P" || The third byte, <code>0x03</code> is unprintable, so it is replaced with the replacement character <code>3F</code> ('?').
 
|-
 
|<code>bytesToString(0x53480350,4,</code>{{Reserved|BYTEORDER.BIG_ENDIAN}}<code>,0x2573);</code> || "SH╳P" || The third byte, <code>0x03</code> is unprintable, so it is replaced with the extended UTF8 replacement character <code>0x2573</code>, the diagonal cross ('╳'). Note for this to be visible in your GUI this character must be present in your font.
 
 
|}
 
|}
  
 
== References ==
 
== References ==
* [http://www.asciitable.com A handy 8-bit ASCII table]
+
 
* [http://lwp.interglacial.com/appf_01.htm A good codepoint table], courtesy of Sean M. Burke
+
[[Category:Numeric Functions]]

Latest revision as of 10:24, 14 October 2016

Function Returns Introduced Description
towards Float/Integer v5.0.207 Adjusts a number by a percentage in a range towards another number

See Also:

Prototypes

Float towards(Float percent, Float current, Float target[, Float snapWithin ]);

Integer towards(Float percent, Integer current, Integer target[, Integer snapWithin ]);

Parameters/Return Value

Parameter Data Type Description
percent Float Percent to move current towards target
current Float
Integer
Current value
target Float
Integer
Target ending value
snapWithin Float
Integer
(optional) When the result would be less than or equal to this distance from the target, the value is "snapped" to the target and the target value is returned. Defaults to 1 (or 1.0f) if not supplied.
Return Float
Integer
A new number "towards" the target

Detailed Description

The towards function moves a current numeric value towards a target value by a percentage.

The function is very useful in moving objects (e.g. gauge needles, sliders, boxes/images) in a fluid manner from their current positions to a target position. For example, when a slider needs to move from pixel position 55 to 85, rather than just assign the slider's position directly to 85 on a change, one can create a much more fluid movement using towards.

The structure of this would use a timer within the object (such as a slider box):

   variable Integer target
   box
       timer oneshot=true value=1 period=1 autoreload=true enabled=false
           listener listeningto=timer.alarm condition=timer.alarm
               script
                   box.ol        = towards(0.33f, box.ol, target);
                   timer.alarm   = false;
                   timer.enabled = (target != box.ol);
           listener listeningto=target
                   timer.enabled = (target != box.ol);

The snapWithin Parameter and the Float Variant of the Function

In the floating point version of this function (where any of the 3 parameters current, target, and/or snapWithin are supplied as floating point values), the returned value will asymptotically approach the target.

By specifying the snapWithin parameter, when the returned value would be less than or equal to a snapWithin distance from the target, the target value is returned.

For example, continuously moving 25% towards a target number using floating point values:

   Float f = 1.0f;
   ...
   (repeated) f = towards(0.25f, f, 5.0f, 0.1f);

Will, upon repetition of the formula, adjust f by 25% towards the target of 5.0 until is within 0.1 of 5.0, at which point 5.0 will be returned.

snapWithin, if not supplied, defaults to 1.0f in the floating point variant of the function.

The snapWithin Parameter and the Integer Variant of the Function

When using integer values, an infinite loop possibility exists where the current never (after repeated assigns) reaches the target.

For example, 33% of the distance between 0 and 1 is always 0 from an integer perspective, and so repeated calls with these parameters will never have the current reach the target.

In a related example, 25% of the distance between 0 and 2 is always 0 from an integer perspective, again causing a potential infinite loop where the current value never moves at all.

There are 2 mechanisms that prevent these behaviors.

First, the remaining distance to the target is internally calculated and truncated. Therefore any fractional amount will cause the current to move by at least 1. This mechanism handles both problematic scenarios above.

The snapWithin, if not supplied, defaults to 1 in the integer variant of the function, and is redundant with the remaining-distance truncation method. However, a designer may choose a greater value to "snap to" a target when within a larger distance.

Troubleshooting and Common Issues

The most common problem with towards occurs when inadvertently you are using the floating point variant of the function with an integer variable as the result. In this case, it is easy to have the function get "stuck" and never reach the target. For example, repeated calls to

current = towards(0.33f, current, target);

if current and target are both integers (Integer, Short, and/or Byte) will converge as expected to the final condition when current is equal to target. This is because the distance remaining between the two values at each iteration is truncated down, and so the value of current always moves at least 1 count every iteration.

However, if inadvertently target is specified as a floating point number, for example if current is 82 and target is 85.0f, the function is promoted to the floating point variant, and the result of the function is now 82.0+(1-0.33)*(85.0-82.0) = 82.99 which, on assignment to current is truncated to 82. Hence the function never converges to the target as expected.

The simple solution to this issue, in this example, is to force the rounding or truncation of the target value as desired into an integer form, making the function use the integer variant:

current = towards(0.33f, current, round(target));

Be careful with attempting this form of solution for this issue:

current = round(towards(0.1f, 82, 85.0f));

as you will find cases (as in this exact example) where the round is ineffectual -- 82.0+(1-0.1)*(85.0-82.0) = 82.3 which still rounds down to 82... hence still non-converging.

Examples

Example Result Notes
towards(0.25f,0,4); 1 25% from 0 to 4 is 1.
towards(0.25f,4,0); 3 25% from 4 to 0 is 3.
towards(0.25f,-4,4); -2 25% from -4 to +4 is 2.
towards(0.25f,3,4); 4 Integer form of function, remaining distance (0.75) is truncated so the return value is 4.
towards(0.25f,3,4.0f,0.25f); 3.25f Floating point form of function
towards(0.75f,3,4.0f,0.25f); 4.00f Floating point form of function; result is 3.75 but is within the 0.25f snapWithin range so result is target.

References