Decimals ?

Doesn’t MT support decimal places ??

[admin@MikroTik] interface> :put (5 / 2)
2

Since when did 5 / 2 = 2 ?

I’m trying to parse the GPS coordinates and convert them back to decimal. Here is a sample script but I cannot get any further since the math is broken.

:global gps [/system gps monitor as-value]

:put ($gps -> "date-and-time")
:put ($gps -> "longitude")

# Parse Longitude
:global long1 [:find ($gps -> "longitude") " " 0]
:global long2 [:find ($gps -> "longitude") " " $long1]
:global long3 [:find ($gps -> "longitude") "'" $long2]
:global long4 [:find ($gps -> "longitude") "''" $long3]

:global degrees [:tonum [:pick ($gps -> "longitude") $long1 $long2 ] ]
:global minutes [:tonum [:pick ($gps -> "longitude") $long2 $long3 ] ]
:global seconds [:tonum [:pick ($gps -> "longitude") ($long3 + 2) $long4 ] ]

:env print

# Should give decimal longitude.
:put ($degrees + ($minutes/60) + ($seconds/3600))

I end up with this:

“gps”={“date-and-time”=“jun/11/2008 18:19:25”; “longitude”=“W 117 12’ 55’'”; “latitude”=“N 33 13’ 49’'”; “altitude”=“191.399994m”; “speed”=“0.111120 km/h”; “valid”=true}
“long1”=1
“long2”=5
“long3”=8
“long4”=12
“degrees”=117
“minutes”=12
“seconds”=55

[master@cip-home] /system gps> :put ($degrees + ($minutes/60) + ($seconds/3600))
117


What happened to my decimals!

If I can get this working I have a script that will upload your routers GPS location to GpsGate.com servers and so you can keep track of your whereabouts on all gps-enabled routers.

Sam

Math is not broken. Router os support only integers.

This will calculate exact decimal values to any decimal places you want. Just plug in dividend, divisor, and decimal places and $result variable will be set to the answer.

# Preforms calculation with decimal points
# dividend / divisor = quotient.decimal = result

:local dividend 3
:local divisor 11
:local decimalplaces 3


# Math Calculation here
:local quotient 0
:local remainder 0
:local result 0
:local decimal 0

:set quotient ($dividend / $divisor)

:if ($quotient = 0) do={
  :set dividend [:tonum ($dividend . "0")]
}

:set remainder ($dividend - ($divisor * $quotient))

:if ($remainder > 0) do={
  :local tmpremainder [:tonum ($remainder . "0")]
  :for x from=1 to=$decimalplaces do={
    :local tmpdecimal [:tonum ($tmpremainder / $divisor)]
    :set decimal [:tonum ($decimal . $tmpdecimal)]
    :set tmpremainder [:tonum (($tmpremainder - ($tmpdecimal * $divisor)) . "0")]
  }
  :set result ($quotient . "." . $decimal)
} else={
  :set result $quotient
}
# END Math Calculation here

:put ($dividend . " / " . $divisor . " = " . $result)

Hope this helps.

2019 Is this still true?

{
local speed 10
:put $speed
:local speedknots ($speed * 5)
:put $speedknots
}
10
50



{
local speed 10
:put $speed
:local speedknots ($speed * 1.5)
:put $speedknots
}
10
Script Error: cannot multiply time interval by ip prefix



{
local speed 10
:put $speed
:local speedknots ($speed * 1,5)
:put $speedknots
}
10
10;5

Yes only integers. Anywhere where you see decimal representation is actually a string.

I know, but MT could add BC or other Linux tool to the script to handle decimal.

Looking forward to v 7.0 :slight_smile:

Disappointed.
Nothing has changes even when 7.1 is released.

It was never promised that something like that will be implemented in ROSv7.

But users can have hope…

Myself, I multiply values by a number like 10 or 100 or 1000 etc. and then exectute the calculation. Then I can see number of decimals I defined by the multiplication factor, I defined before.

How is this done MTTQ module, there are decimals retrieved from devices are normal?

I was told a kernel update get me [:infinity] in v7.∞ someplace too – so disappointing. :wink:



The more complex solution involves ROS’s “ip6” type ( & :toip6 ) – it does have 128-bits available. I’d point you the IETF internet standards here. See RFC8135: Complex Addressing in IPv6 dated April 1, 2017.

Apparently someone thought about what if you had an IPv6 type BUT no “floating point” / “decimal” number type – as is the unusual case on Mikrotik scripts. See Mikrotik’s ip6 type could be overload to applying the idea of using IPv6 types for floating points, since it’s already longer 128bit int.

  • the “::” can act a decimal place (thus parsable and still somewhat readable being in hex with “::” instead of a more typical “.” or “,”).
  • negative number could the starting prefix 1000:: (positive) and 1001:: (negative) which non-routable in IPv6 to use a proxy for 1K complement or needing bit arithmetic.
  • some converting to hex and putting “:” every 4 hex chars with the :: separator being int and fractional parts be need to encode a “decimal”.
  • Some bits be wasted in this approach. The “::” uses 4 for 0x000 “decimal” separator & another 4 for to store the positive/negative. But these additional bits “wasted” in the “1K-complement prefix” offering expansion to the full set of RFC8135, like imaginary or avian carrier datagrams from RFC1149.
  • the integer part be store in the least significant bits, so 1000:8::4 be +4.1 while 1001:2::8 be -8.2 – searching backward on mikrotik quickly give you the int part this way (I think)

How this theoretically work using Mikrotik’s “ip6” type as proxy for a longer int.

:put [$tofloat 0]
                 1000::
:put [$tofloat 1.1]
                   1000:1::1
:put [$tofloat -1.1]
                    1001:1::1
:put [$tofloat 4343.1344]
                         1000:540::10f7

The reverse looks like this:

:put [$fromfloat [$tofloat 0]]
                              0.0
:put [$fromfloat [$tofloat 1.1]]
                                1.1
:put [$fromfloat [$tofloat -1.1]]
                                 -1.1
:put [$fromfloat [$tofloat -123.123]]
                                     -123.123
:put [$fromfloat [$tofloat 4343.1344]]
				4343.1344

Although certainly just multiplying 1000 is easier to proxy decimals is WAY easier. If you needed longer numbers, ip6 type be easier than trying to use an array to store the parts I think. I don’t have a need for floating point number in my script – I did the “int shift” way @msattr suggests likely be best. But read this topic and thought of RFC8135.

I was cleaning up some script files, I’d forgotten to publish a partially, buggy implementation of RFC8135 produced that above output.

Basically it uses two functions right now:
$tofloat - from a string to a “fakefloat” ( the “ip6” type in ROS)
$fromfloat - takes a variable, internally a ip6 type, created by $tofloat to output it as floating/decimal number as string.

There are NO math functions yet, like $ADD … or $MULTIPLY. And $fromfloat should take an arg like “round=0” to get the integer part or truncate the decimal part. Targeting a 4/1 release for those.

And borrows/adapts the following helpers, which may be more helpful as functions:
$tobase base=[1-32, optional] - used to convert base10 to base16 hexstrings
$strip strip= - used to remove “:” in ip6 addresses.

:global tofloat do={
    :global tobase;
    :local i;
    :local d;
    :local ip6comp 1000
    if ($1~"-") do={
        :set ip6comp 1001
        :set $1 [pick $1 1 99]
    }
    :if ([typeof $1]~("num")) do={
        :set $i $1; 
        :set $d 0;
    } else={ :if ([find $1 "."]>0) do={
        :set i [pick $1 0 [find $1 "."]]
        :set d [pick $1 ([find $1 "."]+1) 64]
        } else={:set $i 0; set $d 0;}
    }
    :local ihex [$tobase $i]
    :local dhex [$tobase $d]
    :local float64 [:toip6 "$ip6comp:$dhex::$ihex"]
    #:put "$ihex $dhex $i $d $float64"
    :return $float64
}

:global fromfloat do={
    :global strip
    :local float64 [tostr $1]
    :local neg ""
    :if ($float64~"1001.+") do={:set $neg "-"}
    :set $float64 [pick $float64 5 64]
    :local ipart [$strip char=":" [pick $float64 ([find $float64 "::"]+2) 64]]
    :local dpart [$strip char=":" [pick $float64 0 ([find $float64 ":"]+1)]]
    #if ([typeof [tonum $dpart]]!="num") do={set dpart 0}
    #:put "$float64 $neg $ipart $dpart"
    :set ipart [tonum "0x$ipart"]
    :set dpart [tonum "0x$dpart"]
    :local strfloat "$neg$ipart.$dpart"
    #:put "$strfloat $ipart $dpart"
    :return $strfloat
}

# adapted from msmater http://forum.mikrotik.com/t/cleaning-characters-from-string-for-use-in-variablename/139391/1
:global strip do={
    :local a [tostr $1]
    :local b $char
    :if ([typeof $b]="nil") do={:set b "-"}
    :while ([find $a $b]) do={
        :set $a ("$[:pick $a 0 ([find $a $b]) ]"."$[:pick $a ([find $a $b]+1) ([:len $a])]")}
    :return $a
}


# adapted from System_Convert-Decimal-BaseX by Randy Graham
# :put [$tobase 32 base=16]
:global tobase do={
    :local decnum $1
    :local basenum ""

    :if ([typeof $base]="nil") do={:local base 16} else={:local base}
    :if ($base>0 and $base<33) do={} else={:set $base 16}
    :local chrtable [:toarray "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f"]
    :local chrtable ($chrtable + [:toarray "g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"])
    :local basedigits 0
    :local basearray
    :local bw
    :local loop 1
    :while ($loop=1 ) do={
            :set bw 0
            :for c from=0 to=$basedigits step=1 do={
            :set bw ($bw*$base)
            :if ($bw=0) do={:set bw 1}
        }
        :set basearray ($basearray + [:toarray $bw])
        :set bw ($decnum/$bw)
        :if ($bw > 0) do={
            :set basedigits ($basedigits+1)
        } else={
            :set loop 0
            :set basedigits ($basedigits-1)
        }
    }
    :local dn $decnum
    :local bpv
    :for c from=$basedigits to=0 do={
        :set bpv ($dn/[:pick $basearray $c])
        :set dn ($dn-([:pick $basearray $c]*$bpv))
        :set basenum "$basenum$[:pick $chrtable $bpv]"
    }
    :return $basenum
}

If you save that to the router as a file called “fakefloat”, usage like:

:import fakefloat
:global myfloat [$tofloat -10.1]
:put [$fromfloat $myfloat]
# -10.1

With some tests like this:

:put  "$([$tobase 1]) $([$tobase 10]) $([$tobase 16]) $([$tobase 16 base=32])" 

:put "stripchar $([$strip char=":" "1:1:1:1" ])"

:put [$fromfloat [toip6 "1000:1::1"]]
:put [$fromfloat [toip6 "1001:1::1"]]
:put [$fromfloat [toip6 "1000:10::10"]]
:put [$fromfloat [toip6 "1001:10::10"]]
:put [$fromfloat [:toip6 "1001:03e9::1"]]
:put [$fromfloat [:toip6 "1001:1::03e9"]]
:put [$fromfloat [:toip6 "1000:540::10f7"]]

:put [$tofloat 0]
:put [$tofloat 1.1]
:put [$tofloat -1.1]
:put [$tofloat -123.-123]
:put [$tofloat 4343.1344]

:put [$fromfloat [$tofloat 0]]
:put [$fromfloat [$tofloat 1.1]]
:put [$fromfloat [$tofloat -1.1]]
:put [$fromfloat [$tofloat -123.123]]
:put [$fromfloat [$tofloat 4343.1344]]
  • only tested on V7.x