ZLib -- Zarqon's useful function library

Fluxxdog

Active member
I had to make another compensation. If the decimal part was less than .1, it wouldn't include the zeros. 12.0108... would become 12.1. Now it works like it should.
 

zarqon

Well-known member
Poking around with this led me to discover a case where all of the functions thus far (ZLib's original, fluxxx's, Theraze's, and the one I was just toying with) fail: for a number like 9.999999, that rounds up to 10. The current rnum() calls that 9.0, and both fluxx's and my experimenting thus far have called it 9.1.

Additionally, the recent fixes commented out an important check, so they fail for numbers between 0 and -1, flipping them to positive.

Not meaning to be negative here though -- thanks for the help, it's given me some good ideas. This is what I've come up with so far, which works:

PHP:
string string_num(float n, int place) {
   if (to_float(round(n)) == n || place < 1) return rnum(round(n));
   boolean neg = (n < 0); n = abs(n);
   matcher numbits = create_matcher("\\.\\d{1,"+place+"}[5-9]",to_string(n));
   if (numbits.find()) n += to_float(1.0/10**place);
   buffer res;
   res.append(rnum(truncate(n)));
   if (neg) res.insert(0,"-");
   numbits = create_matcher("\\.\\d{1,"+place+"}",to_string(n));
   if (numbits.find()) res.append(numbits.group(0));
   return to_string(res);
}

Unfortunately, it's more expensive than any of the other methods. I'm brainstorming ways to lighten its footprint, up to and including a completely new algorithm. I really do like using a regex to decide whether or not we need to round, though -- I just feel like there should be some way to re-use that regex rather than making a new one; I just haven't thought of it yet. Unfortunately, since rounding the decimal portion may change the number before the decimal, compensation for rounding has to be made before beginning to text-ize the number.

I'll keep poking around, I guess.

ETA: Here's another solution, using ZLib's old code as a starting point instead. Profiler shows it to be the fastest so far:

PHP:
string rnum(float n, int place) {
   if (to_float(round(n)) == n || place < 1) return rnum(round(n));
   int ni = round(n*10.0**place);
   buffer res;
   res.append(rnum(ni/10**place)+".");
   if (n < 0 && n > -1) res.insert(0,"-");
   string ns = to_string(ni);
   res.append(substring(ns,ns.length()-place));
   return to_string(res);
}

I really don't like the fact that 4.00001 gets shown as "4.00" though. I don't care about significant digits, the point of rnum() is to make a human-readable number, which means no pointless 0's. Grrr.
 
Last edited:

Theraze

Active member
Another issue tied into the new, accurate floats:
New Version of CustomDaily Available:
Upgrade CustomDaily from 2.200000047683716 to here: http://kolmafia.us/showthread.php?t=3443

Version checker now has 'accurate' floats. Which may or may not be an issue, depending on your luck. :) But we might need to parse check_version through some form of rnum that eliminates anything not numerically related.

And yes, StDoodle, I know that script hasn't been supported for over a year, but I like having the quick reminder of remaining activities in my charsheet. Running another relay script to remind me what to do? Ha... never works. :) If I remember I need it, I've already used whatever.

Anyways, that's just the only script with an 'upgrade' notification today. Guessing anything else with a float-type version number would do the same.

Edit: And I copied that into my rnum, though I did take the two debug-print bits out.
 

Fluxxdog

Active member
Poking around with this led me to discover a case where all of the functions thus far (ZLib's original, fluxxx's, Theraze's, and the one I was just toying with) fail: for a number like 9.999999, that rounds up to 10. The current rnum() calls that 9.0, and both fluxx's and my experimenting thus far have called it 9.1.

Additionally, the recent fixes commented out an important check, so they fail for numbers between 0 and -1, flipping them to positive.

Not meaning to be negative here though -- thanks for the help, it's given me some good ideas.
Ha! Negative! Funny ^^ Seriously, I think if we could help, that's what's important. And thank you for your work.
 

zarqon

Well-known member
And now Jason has made things much easier with r11055. I was expecting this at some point due to his "for now" talk of r11042, but had also expected to need something in the interim.

I can put out an update soon now.
 

Bale

Minion
Actually, this kinda makes most of rnum unnecessary.


Code:
> ash to_string(12345.196, "%,.2f")

Returned: 12,345.20

I suppose the new purpose of rnum is to remove the trailing zeroes, eh?
 

zarqon

Well-known member
The new purpose of rnum() is to be easy to remember without consulting documentation on String.format(), and to automatically differentiate floats from ints.

PHP:
string rnum(int n) {
   return to_string(n,"%,d");
}
string rnum(float n, int place) {
   if (place < 1 || to_float(round(n)) == to_float(to_string(n,"%,."+place+"f"))) return rnum(round(n));
   return to_string(n,"%,."+place+"f");
}
string rnum(float n) { return rnum(n,2); }

This still doesn't remove trailing 0's, which was something I liked in the old version. It also sometimes returns "-0.00" for very small negative floats. I'm trying to figure out how to do that with String.format(), but if I can't, we can at least remove trailing 0's with a regex or somesuch.

Look at me, casually talking about using regexes to solve my problems!

EDIT: Didn't need a regex! Edited above code with solution.
 
Last edited:

slyz

Developer
Look at me, casually talking about using regexes to solve my problems!
Take out the tongs!

PHP:
string rnum(int n) { 
   return to_string(n,"%,d"); 
}
string rnum(float n, int place) { 
   if (place < 1 || to_float(round(n)) == to_float(to_string(n,"%,."+place+"f"))) return rnum(round(n)); 
   string ret = to_string(n,"%,."+place+"f");
   matcher TRAILING_ZERO_MATCHER = create_matcher( "(?<=\\d,\\d{0,"+place+"}[1-9]?)0+(?=\\D|$)", ret );
   ret = TRAILING_ZERO_MATCHER.replace_all( "" );
   matcher TRAILING_COMMA_MATCHER = create_matcher( "(?<=\\d),(?=\\D|$)", ret );
   ret = TRAILING_COMMA_MATCHER.reset( ret ).replace_all( "" );
   return ret;
}
string rnum(float n) { return rnum(n,2); }
 

zarqon

Well-known member
Nice! First I was thrown off by the "trailing comma" bit, since for me those happen in the middle of the integer part. I'd like to retain the locale flexibility granted by the new ASH feature, which means the regex would have to allow for different decimal separators (and grouping separators, which presents a complication for matching since they may be exactly flipped in one locale vs. another).

It might also be helpful to know that at this point in the code there is guaranteed to be at least one nonzero after the decimal. Numbers that would have only zeros (or nothing) after the decimal are truncated to ints by the first line.

Given all that, just this little guy actually works fine:

PHP:
ret = replace_all(create_matcher( "0+$", ret ), "");

The little fellow is so cute I almost put down the tongs and scratched him behind the ear. Fortunately I remembered in the nick of time that regexes are all savage wild beasts at heart and not to be trusted.
 

Bale

Minion
Fortunately I remembered in the nick of time that regexes are all savage wild beasts at heart and not to be trusted.

Good thing you remembered that. Here's the problem:

Code:
> ash string ret = "4.00"; replace_all(create_matcher( "0+$", ret ), "")

Returned: 4.

I'm pretty sure you wanted to see either 4.0 or 4, but you did not get either of those.
 

slyz

Developer
Code:
> ash string ret = "400"; replace_all(create_matcher( "0+$", ret ), "") ;

Returned: 4
I'm pretty sure you wanted to see 400 :)
 

zarqon

Well-known member
Here's the function:

PHP:
string rnum(int n) {
   return to_string(n,"%,d");
}
string rnum(float n, int place) {
   if (place < 1 || to_float(round(n)) == to_float(to_string(n,"%,."+place+"f"))) return rnum(round(n));
   return replace_all(create_matcher("0+$", to_string(n,"%,."+place+"f")),"");
}
string rnum(float n) { return rnum(n,2); }

That first line is catching the things you guys are reporting. As I said, by the time we're running the matcher, there is guaranteed to be a nonzero after the decimal.
 

Bale

Minion
That's our zarqon! He doesn't trust a regexp any further than he can throw it. Though I'd like to see a regexp that can handle the whole problem. So far I'm up to this:

Code:
> ash string ret = "4.00"; replace_all(create_matcher( "(?<!\\.)0+$", ret ), "")

Returned: 4.0

> ash string ret = "400"; replace_all(create_matcher( "(?<!\\.)0+$", ret ), "")

Returned: 4

It doesn't handle the problem slyz pointed out.
 
Last edited:

zarqon

Well-known member
This would work for almost the whole problem:

Code:
\D(\d*?)(0+$)

You still need to delete either group 0 or group 2, depending on whether group 1 is empty.
 

zarqon

Well-known member
The work on item value functions was interrupted with the flurry of activity about the change to floats, and then by the conspiracy of illness and other things to prevent me from playing KoL. I even missed out on limited content! Lord Flamewho? Sucks.

Anyway, I'm trying to climb back on top of things, and I've got two functions (technically 8 due to overloading) which I believe cover all the bases:

int mall_val(item it, [ float expirydays ], [ boolean combatsafe ] )
int sell_val(item it, [ float expirydays ], [ boolean combatsafe ] )


First, mall_val() is a wrapper for ASH's mall_price() and historical_price(). In fact, calling this function with neither of the optional parameters is identical to calling mall_price(), since expirydays defaults to 0 and combatsafe defaults to false. In practice, though, you'll probably be calling it with one of the optional parameters, but not both.

How does it work? It returns the value of the item in the mall, preferring historical_price() if it exists and isn't outdated (as specified by expirydays). Omitting expirydays will skip checking the historical price and always check mall_price(), and specifying expirydays of 100 or greater means it will always use historical price if it exists regardless of age (necessary since I observed historical_age() returning 9223372036854775807 on some occasions). Specifying combatsafe as true overrides expirydays, as it will avoid calling mall_price() and thus prefer historical price no matter the age, returning 0 if there is no historical_price().

The second function, sell_val(), is more concerned with the meat you could realistically make from selling that item. It consults the first function, and returns that value if it is more than mall minimum. Otherwise, it returns the autosell value. It has the same parameters as the first.

Thus, with these flexible functions we can handily accomplish the functionality of all of the functions posted previously and then some!

In case you read ASH better than English -- or conversely, in case I write ASH more clearly than I do English explaining ASH -- here they are:

PHP:
int mall_val(item it, float expirydays, boolean combatsafe) {
   if (!is_tradeable(it)) return 0;
   if (historical_price(it) > 0 && (combatsafe || expirydays > 99 || historical_age(it) < expirydays)) return historical_price(it);
   return combatsafe ? 0 : mall_price(it);
}
int mall_val(item it, float expirydays) { return mall_val(it,expirydays,false); }
int mall_val(item it, boolean combatsafe) { return mall_val(it,0,combatsafe); }
int mall_val(item it) { return mall_val(it,0,false); }

int sell_val(item it, float expirydays, boolean combatsafe) {
   int mall = mall_val(it,expirydays,combatsafe);
   if (mall > max(100,2*autosell_price(it))) return mall;
   return autosell_price(it);
}
int sell_val(item it, float expirydays) { return sell_val(it,expirydays,false); }
int sell_val(item it, boolean combatsafe) { return sell_val(it,0,combatsafe); }
int sell_val(item it) { return sell_val(it,0,false); }

I'll give these functions a day or two to percolate and get thumbs of approval, critiques of usefulness, or insults of scathingness, and if they make it through either unscathed or improved, we'll spin a ZLib update including these and the rnum() fix, which means we can then spin the long-awaited BatBrain update at the same time.
 
Last edited:

slyz

Developer
Here is something I added to networth.ash to return the mall price of the cheapest form of foldable items:
PHP:
int get_price( item itm, boolean historical )
{
	int [ item ] forms = itm.get_related( "fold" );
	if ( forms.count() == 0 )
	{
		return historical ? itm.historical_price() : itm.mall_price();
	}

	int [ int ] prices;
	foreach f in forms
	{
		if ( !f.is_tradeable() ) continue;
		prices[ prices.count() ] = historical ? f.historical_price() : f.mall_price();
	}
	sort prices by value;
	return prices[ 0 ];
}
Here is also a quick and dirty function to estimate of the value of an item when it is pulverized and the proceeds are autosold:
PHP:
float smash_value( item it )
{
	float value = 0; 
	int [ item ] smash_yield = get_related( it, "pulverize" );
	if ( !have_skill( $skill[ Pulverize ] ) || smash_yield.count() == 0 )
	{
		return value;
	}
	foreach yield, q in smash_yield
	{
		value = value + yield.autosell_price().to_float() * q / 1000000.0;
	}
	return value;
}
I probably stole this one from Bale (or from you!).
 

zarqon

Well-known member
Handy! Were those intended as suggestions?

In the interest of fixing things, updates have been spun regardless. New ZLib adding the above value functions and fixing rnum(), and new BatBrain with multifarious small-but-possibly-crucial-to-you fixes. Also, turned over a new leaf and documented the functions on the ZLib Wiki page on the same day I added them!
 
Top