Ceil() with precision Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Ceil() with precision

22 Nov 2019, 12:24

Edit: :arrow: Updated.

@guest3456, I modified the function a little,

Code: Select all

ceiling(param_number, param_precision:=0) {
    ;// based off:  https://stackoverflow.com/a/48933199/312601
    offset := 0.5
    if (param_precision != 0)
      offset := offset / (10**param_precision)
	
	if (param_precision >= 1) {
		; For decimal formatting:
		; If the input number is a string with more decimal characters than the desired precision,
		; format the sum to one more decimal place than the input, but if the param_precision
		; is greater than the number of decimal characters, use that number + 1 instead.
		; Using the param_precision number is useful when the input is a pure float (cached) 
		; and param_precision is greater than the setformat precision.
		n_dec_char := strlen( substr(param_number, instr(param_number, ".") + 1) )			; count the number of decimal characters.
		sum := format("{:." max(n_dec_char, param_precision) + 1 "f}", param_number+offset)
	}
	else 
		sum := param_number+offset
	sum := trim(sum, "0")	; trim zeroes.
	value := (SubStr(sum, 0) = "5") ? SubStr(sum, 1, -1) : sum        ;// if last char is 5 then remove it
    result := Round(value, param_precision)
    ; msgbox %sum%`n%value%`n%result%
    return result
}
In particular this solves the following issue with the previous modification I posted,

Code: Select all

msgbox % ceiling(2.22000000000000020, 2) ; before: 2.22, now 2.23
That is, if you input the actual string 2.22000000000000020, it should consider the entire input.

Cheers.
Last edited by Helgef on 24 Nov 2019, 04:02, edited 1 time in total.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Ceil() with precision

23 Nov 2019, 16:48

PS negative numbers. E.g. Ceil(2.5) = 3, Ceil(-2.5) = -2.
Counterintuitively, to ceil a negative number, you remove the sign, *floor* the number, and restore the sign.

Here is a test script and my current and former functions. Any feedback is welcome.

Code: Select all

;q:: ;test Ceil function

vList := ""
VarSetCapacity(vList, 1000000*2)

;num|precision|answer (intuitive)|answer (mathematical)
vList .= " ;continuation section
(
4.05|0|5
4.005|0|5
4.1|0|5
41|0|41
6.004|2|6.01
6.004|1|6.1
6040|-2|6100
6040|-3|7000
2.22|2|2.22|2.23

2.22000000000000020|2|2.22|2.23

2.2|1|2.2|2.2
2.22|2|2.22|2.23
2.222|3|2.222|2.222
2.2222|4|2.2222|2.2222
2.22222|5|2.22222|2.22222
2.222222|6|2.222222|2.222222
2.2222222|7|2.2222222|2.2222222

-4.05|0|-4
-4.005|0|-4
-4.1|0|-4
-41|0|-41
-6.004|2|-6.00
-6.004|1|-6.0
-6040|-2|-6000
-6040|-3|-6000
-2.22|2|-2.22

-2.22000000000000020|2|-2.22

-2.2|1|-2.2
-2.22|2|-2.22
-2.222|3|-2.222
-2.2222|4|-2.2222
-2.22222|5|-2.22222
-2.222222|6|-2.222222
-2.2222222|7|-2.2222222
)"

vLen := 4
vPfx := ""
vList .= "`n`n"
Loop % 10**vLen
{
	vNum := "" vPfx Format("0.{:0" vLen "}", A_Index-1)
	vList .= vNum "|" vLen "|" vNum "`n"
}
vPfx := "-"
vList .= "`n`n"
Loop % 10**vLen
{
	if (A_Index = 1) ;skip '-0'
		continue
	vNum := "" vPfx Format("0.{:0" vLen "}", A_Index-1)
	vList .= vNum "|" vLen "|" vNum "`n"
}
;MsgBox, % vList

oFunc := Func("JEE_Ceil")
;oFunc := Func("JEE_CeilAlt")
;oFunc := Func("helgef_ceiling")

Loop Parse, vList, % "`n", % "`r"
{
	if (A_LoopField = "")
	{
		;vOutput .= "`r`n"
		continue
	}

	oTemp := StrSplit(A_LoopField, "|")
	vNum := oTemp[1]
	vDP := oTemp[2]
	vAnsI := oTemp[3]
	vAnsM := oTemp.HasKey(4) ? oTemp[4] : oTemp[3]

	vTemp := ""

	vAns1 := %oFunc%("" vNum, vDP)
	vResult := ""
	("" vAns1 = "" vAnsI) && (vResult .= "I")
	("" vAns1 = "" vAnsM) && (vResult .= "M")
	(vResult = "") && (vResult := "E")
	(vResult = "IM") && (vResult := "")
	vTemp .= vResult

	vAns2 := %oFunc%(0 + vNum, vDP)
	vTemp .= "_"
	vResult := ""
	("" vAns2 = "" vAnsI) && (vResult .= "I")
	("" vAns2 = "" vAnsM) && (vResult .= "M")
	(vResult = "") && (vResult := "E")
	(vResult = "IM") && (vResult := "")
	vTemp .= vResult

	;MsgBox, % A_LoopField " " vTemp
	if !(vTemp = "_")
		vOutput .= A_LoopField " " vTemp " " vAns1 "_" vAns2 "`r`n"
}
Clipboard := vOutput
MsgBox, % "done"
return

;==================================================

;e.g. vDP: 2, round up to 2 decimal places
;e.g. vDP: -2, round up to nearest 100

;note: if vDP < 15, rounds to 15dp before ceiling
;e.g. JEE_Ceil(2.22, 2) = 2.22
;e.g. JEE_Ceil(2.2200000000000002, 2) = 2.22
;even though (2.22 == 2.2200000000000002) = 1
;and so both numbers in principle should ceil to 2.23
;rounding to 15dp occurs to give a more intuitive result
;i.e. by '2.2200000000000002' we probably meant '2.22'
JEE_Ceil(vNum, vDP:=0)
{
	local
	static vIsV1 := !!SubStr(1, 0)
	if !vDP
		return Ceil(vNum)
	else if (vDP < 0)
		return Ceil(Ceil(vNum * (10**vDP)) * (10**-vDP))
	else if (vDP >= 15)
		;return "" Format("{:0." vDP "f}", Ceil(vNum * (10**vDP)) * (10**-vDP))
		return "" Round(Ceil(vNum * (10**vDP)) * (10**-vDP), vDP)
	else
	{
		vIsNeg := (vNum < 0)
		;round to 15 decimal places to discard some precision,
		;e.g. 2.22000000000000020 becomes 2.22
		vNum := "" RTrim(Round(vNum, 15), "0")
		vCount := StrLen(vNum) - InStr(vNum, ".")
		if (vCount = vDP)
			return vNum
		else if (vCount < vDP)
			return vNum Format("{:0" (vDP-vCount) "}", 0)
		;9223372036854775807 max int64 (19-digit number)
		vNum := SubStr(vNum, 1, vDP-vCount)
		if vIsNeg
			return vNum
		if (StrLen(vNum) >= 19+1) ;note: +1 for the decimal point
			;throw Exception("", -1)
			;return "" Format("{:0." vDP "f}", Ceil(vNum * (10**vDP)) * (10**-vDP))
			return "" Round(Ceil(vNum * (10**vDP)) * (10**-vDP), vDP)
		vNum := StrReplace(vNum, ".")
		vNum := Ceil(vNum) + 1
		return "" Format("{:01}", SubStr(vNum, 1, -vDP)) "." Format("{:0" vDP "}", SubStr(vNum, vIsV1-vDP)) ;note: use Format to convert blank string to 0
	}
}

;==================================================

;e.g. vDP: 2, round up to 2 decimal places
;e.g. vDP: -2, round up to nearest 100

;note: does not round before ceiling
;e.g. JEE_CeilAlt(2.22, 2) = 2.23
;e.g. JEE_CeilAlt(2.2200000000000002, 2) = 2.23
;both give the same result since (2.22 == 2.2200000000000002) = 1
JEE_CeilAlt(vNum, vDP:=0)
{
	local
	if !vDP
		return Ceil(vNum)
	else if (vDP > 0)
		;return "" Format("{:0." vDP "f}", Ceil(vNum * (10**vDP)) * (10**-vDP))
		return "" Round(Ceil(vNum * (10**vDP)) * (10**-vDP), vDP)
	else ;if (vDP < 0)
		return Ceil(Ceil(vNum * (10**vDP)) * (10**-vDP))
}

;==================================================
[EDIT:] Added a fix re. numbers with leading zeros.

The function treats 2.22000000000000020 and "2.22000000000000020" in the same way, and returns 2.22 for both, when 2dp is specified.

I am also considering writing a function that does all checks/manipulations via strings. I.e. part of a maths via strings library.
Last edited by jeeswg on 24 Nov 2019, 08:49, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Ceil() with precision  Topic is solved

24 Nov 2019, 04:00

It seems like the SO algorithm doesn't handle negative numbers, I've applied a fix,

Code: Select all

ceiling(param_number, param_precision:=0) {
    ;// based off:  https://stackoverflow.com/a/48933199/312601
	if (param_precision == 0) ; regular ceil
		return ceil(param_number)
	offset := 0.5 / (10**param_precision)
	if (param_number < 0 && param_precision >= 1) 
		offset //= 10 ; adjust offset for negative numbers and positive param_precision
	if (param_precision >= 1) {
		; For decimal formating:
		; If the input number is a string with more decimal characters than the desired precision,
		; format the sum to one more decimal place than the input, but if the param_precision
		; is greater than the number of decimal characters, use that number + 1 instead.
		; Using the param_precision number is useful when the input is a pure float (cached) 
		; and param_precision is greater than the setformat precision.
		n_dec_char := strlen( substr(param_number, instr(param_number, ".") + 1) )			; count the number of decimal characters.
		sum := format("{:." max(n_dec_char, param_precision) + 1 "f}", param_number+offset)
	}
	else 
		sum := param_number+offset
	sum := trim(sum, "0")	; trim zeroes.
	value := (SubStr(sum, 0) = "5") && param_number != sum ? SubStr(sum, 1, -1) : sum        ;// if last char is 5 then remove it unless it is part of the original string.
    result := Round(value, param_precision)
    ; msgbox %sum%`n%value%`n%result%
    return result
}
Thanks for point that out jeeswg.

Cheers.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Lpanatt and 310 guests