• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

BigNumber Class

Status
Not open for further replies.

EdgeOfChaos

E

EdgeOfChaos

Currently, using any number over MAXINT (about 2.14 billion) will cause the number to overflow and go into some negative. I was encountering this problem in my map, so I wrote a class to fix it.

This represents a number in scientific notation, created by many various methods. Then when you want to use it, you can use the take() method to take as much as possible (either 2.14billion or everything remaining) out and return it as a real to be used, until the number is empty (which can also be checked), so this can be done via a loop, if you want to deal damage or something.

I'm looking for some feedback on how to improve this. It's functional right now, but feels like it could be better. Also, I'm wondering if anyone else would actually find this useful. Currently, it supports creating a number from a number and an exponent and can do so by a string also (like "4.5 * 10^12"), can multiply two together, and use the number.

The Hive Workshop is deleting my indentation, cool feature of hive 2.0. Sorry.

JASS:
/**
*  Representation of a very large number (greater than MAXINT)
*  This is achieved by putting the numbers into scientific notation, making them
*  easier to represent
*  For a standard real, any values above 2.14b or so will become negative.
*  This is intended to solve this problem and make very large
*  numbers possible to work with.
*  Does not handle negative numbers. Behavior with negatives is undefined
*  @version 1.0
*  @author EdgeOfChaos
*/
struct BigFloat

  //Stores the number part of the scientific number
  private real number
  //Stores the exponential part - i.e. 10^25
  private integer exponent

  /**
  *  Default no-args constructor for a large number
  */
  public static method create takes real n, integer e returns thistype
  local thistype new = thistype.allocate()
  call new.change(n,e)
  return new
  endmethod

  /**
  *  Gets the number value of this BigFloat
  */
  public method getNum takes nothing returns real
  return this.number
  endmethod

  /**
  *  Gets the exponent of this BigFloat
  */
  public method getExp takes nothing returns integer
  return this.exponent
  endmethod

  /**
  *  Detects whether this big number is empty or not
  *  Defined as empty if it is 0 * 10^0
  */
  public method isEmpty takes nothing returns boolean
  return (this.getNum() <= 0.001)
  endmethod

  /**
  *  Takes as much of this BigFloat as possible and return it as a real
  *  This will be either 2147483646 or all remaining
  */
  public method take takes nothing returns real
  local real toRemove = 2147483646
  if(isEmpty()==false)then
  if((this.exponent < 9) or (this.exponent==9 and this.getNum <= 2.147483646))then
  set toRemove = this.number * Pow(10,this.exponent)
  set this.exponent = 0
  set this.number = 0
  return toRemove
  endif
  set toRemove = toRemove / Pow(10,this.exponent)
  set this.number = this.number - toRemove
  if(this.number <= 0)then
  set this.number = 10 - this.number
  set this.exponent = this.exponent-1
  endif
  endif
  return I2R(R2I(toRemove))
  endmethod

  private method change takes real n, integer e returns nothing
  set this.number = n
  set this.exponent = e
  call this.compress()
  endmethod

  private method compress takes nothing returns nothing
  loop
  exitwhen this.getNum() < 10.
  set this.number = this.number / 10
  set this.exponent = this.exponent + 1
  endloop

  if(this.getNum() <= 0) then
  set this.number = 0
  set this.exponent = 0
  elseif(this.getNum() < 1) then
  loop
  exitwhen this.getNum() >= 1.
  set this.number = this.number * 10
  set this.exponent = this.exponent - 1
  endloop
  endif
  endmethod

  /**
  *  Returns the scientific notation
  *  of the big number.
  */
  public method toString takes nothing returns string
  return R2S(number) + " * 10^" + I2S(exponent)
  endmethod

  /**
  *  Destroys this big number.
  */
  public method destroy takes nothing returns nothing
  call this.deallocate()
  endmethod

  /**
  *  Creates a new BigFloat from the specified real num
  *  Num must be less than MAXINT.
  */
  public static method convert takes real num returns thistype
  local real numFinal = num
  local integer expFinal = 0
  return thistype.create(numFinal,expFinal)
  endmethod

  /**
  *  Changes the specified scientific notation string
  *  into a BigFloat
  *  Strings MUST be in the format:
  *  5 * 10^15 for example
  */
  public static method fromScientific takes string num returns thistype
  local real numberSection = 0.
  local integer exponentSection = 0
  local integer pointer = 0
  local integer breakCheck = 0
  local string temp = ""

  loop
  exitwhen ((breakCheck > 200) or (SubString(num,pointer-1,pointer)==" "))
  set temp = SubString(num,0,pointer)
  set pointer = pointer + 1
  set breakCheck = breakCheck + 1
  endloop

  set numberSection = S2R(temp)

  loop
  exitwhen ((breakCheck > 200) or (SubString(num,pointer-1,pointer)=="^"))
  set pointer = pointer + 1
  set breakCheck = breakCheck + 1
  endloop

  set temp = SubString(num,pointer,200)
  set exponentSection = S2I(temp)

  return thistype.create(numberSection,exponentSection)
  endmethod

  /**
  *  Multiplies this with another big number
  *  If destroy is set to true, the second BigFloat will be destroyed after the t
  */
  public method multiply takes BigFloat by, boolean destroy returns nothing
  local real numFinal = by.getNum() * this.getNum()
  local integer expFinal = by.getExp() + this.getExp()

  if(destroy)then
  call by.destroy()
  endif
  call this.change(numFinal,expFinal)
  endmethod

  /**
  *  Divides this with another big number
  *  If destroy is set to true, the second BigFloat will be destroyed after the t
  */
  public method divide takes BigFloat by, boolean destroy returns nothing
  local real numFinal = this.getNum() / by.getNum()
  local integer expFinal = this.getExp() - by.getExp()
  if(destroy)then
  call by.destroy()
  endif
  call this.change(numFinal,expFinal)
  endmethod

  /**
  *  Adds another big number
  *  If destroy is set to true, the second BigFloat will be destroyed after the t
  */
  public method add takes BigFloat by, boolean destroy returns nothing
  local real numFinal = 0.
  local integer expFinal = 0
  local integer diff = this.getExp() - by.getExp()
  if(diff==0)then
  set numFinal = this.getNum() + by.getNum()
  set expFinal = this.getExp()
  elseif(diff>0)then
  set numFinal = this.getNum() + (by.getNum() / (Pow(10,diff)))
  set expFinal = this.getExp()
  else
  set diff = -diff
  set numFinal = by.getNum() + (this.getNum() / (Pow(10,diff)))
  set expFinal = by.getExp()
  endif

  if(destroy)then
  call by.destroy()
  endif
  call this.change(numFinal,expFinal)
  endmethod

  /**
  *  Subtracts another big number
  *  If destroy is set to true, the second BigFloat will be destroyed after the t
  */
  public method subtract takes BigFloat by, boolean destroy returns nothing
  local real numFinal = 0.
  local integer expFinal = 0
  local integer diff = this.getExp() - by.getExp()
  if(diff==0)then
  set numFinal = this.getNum() - by.getNum()
  set expFinal = this.getExp()
  elseif(diff>0)then
  set numFinal = this.getNum() - (by.getNum() / (Pow(10,diff)))
  set expFinal = this.getExp()
  else
  call change(0,0)
  endif

  if(destroy)then
  call by.destroy()
  endif
  call this.change(numFinal,expFinal)
  endmethod

  public method percentOf takes BigFloat small, boolean destroy returns real
  local real percent = 0.
  call small.divide(this,false)
  set percent = small.take() * 100
  if(destroy)then
  call small.destroy()
  endif
  return percent
  endmethod

  /**
  *  Copies the old BigFloat to a new one
  */
  public static method copy takes BigFloat old returns thistype
  return thistype.create(old.getNum(),old.getExp())
  endmethod
endstruct
 
Last edited by a moderator:
Level 6
Joined
Jul 30, 2013
Messages
282
Like TriggerHappy said for working with large integers there is already Nestharus' BigInt library.

And this system seems to be essentially no better than just using a real so why would i not just use reals in stead. (jass2 reals are essentially scientific notation, see IEEE floating point - Wikipedia, the free encyclopedia for gory details.)

so no i dont see this getting much interest as it is now.

i could see some value in making an API wrapper for some of Nes' libraries tho, he is infamous for making brilliant stuff but with way-too-leaky apis.
 

EdgeOfChaos

E

EdgeOfChaos

Jass reals are not essentially scientific notation. I know that reals in other languages are, but Blizzard did it differently for some reason. You've probably never encountered this problem if you've never used huge numbers in a map.

Anyways if they were, you would expect them to be able to store values up to like 10e130 or something. So try this code, which passes a real of 3 billion and displays it, see what happens.
JASS:
scope RealTest initializer onInit

  private function display takes real r returns nothing
  call BJDebugMsg(R2S(r))
  endfunction

  private function onInit takes nothing returns nothing
  call display(3000000000.0)
  endfunction

endscope
Answer: it shows about negative 1.2 billion, obviously not correct. I did also mention this in the opening post.

Nestharus's thing is unclear how to use it. And I want a larger decimal number, not a larger integer and I don't care about the base of the number, so it's completely different anyways.

Seriously: why can no one on this forum be bothered to read? Every time I make a thread here, people read the title and then comment something either completely unrelated, or something I already answered. Well I was asking if anyone else had interest in this, and the answer is that you couldn't even be bothered to read a few words of the post and give relevant feedback, so I guess not!
 
Last edited by a moderator:

EdgeOfChaos

E

EdgeOfChaos

In other languages, yes, but in JASS the max number for reals is 2,147,483,647. Any value above it will set it to an incorrect value. If you want to see it for yourself, run this code.
JASS:
scope RealTest initializer onInit

  private function display takes real r returns nothing
  call BJDebugMsg(R2S(r))
  endfunction

  private function onInit takes nothing returns nothing
  call display(3000000000.0)
  endfunction

endscope
 
Damage can also go past 2.1bla billion, just not on the units info panel since its represented by integers.

Edit: Oh, just noticed you said incorrect value. . . Yeah the accuracy isn't 100% after the soft max, floating point error I think it is called. Think DSG mentions that a lot, though I see a repeat of some kind. . . Might find a way to counter it if I am correct otherwise it isn't too far off though if your really worried about accuracy then giant numbers aren't a good idea to begin with.
 

Attachments

  • getting pass 2.2 billion health.w3x
    19.3 KB · Views: 89
Last edited:

EdgeOfChaos

E

EdgeOfChaos

I never knew you could do some trickery with handicaps to get stuff above the limit, that's interesting. But until you show me a real variable holding over 2.1b in the editor and being able to use it properly (display and damage, for example) I stick by my initial statement. And I know for a fact that dealing over 2.1b damage with an attack is impossible - I had a unit with 2 billion damage and gave him Inner fire, and he dealt zero. Your test map shows that too, I pressed escape 3 times and it no longer deals damage.
 
I never knew you could do some trickery with handicaps to get stuff above the limit, that's interesting. But until you show me a real variable holding over 2.1b in the editor and being able to use it properly (display and damage, for example) I stick by my initial statement. And I know for a fact that dealing over 2.1b damage with an attack is impossible - I had a unit with 2 billion damage and gave him Inner fire, and he dealt zero. Your test map shows that too, I pressed escape 3 times and it no longer deals damage.

Amazingly there is still so much that hasn't been posted publicly yet.

Sure. . . Make two real variables, one represent 1 while the other represents 1 billion. Otherwise store the real variable on a unit and grab it from there.

Well that is part of the units info panel which is why, try using critical strike or bash or even better, the best damage bonus ability in WC3 without triggers being Demolish. I am not sure about bash yet since I haven't tested that however any damage bonus ability not part of the info panel should work.

Edit: Modifying the damage through a damage engine/system works too.
 
Level 6
Joined
Jul 30, 2013
Messages
282

i think you don't understand what a scientific notations is.. nor what precision is.

a float can easily represent values above 2.1 billion what it cannot do however is represent every integral value possible.
jass2 reals are 32bit binary floats essentially.

a float consists of 3 parts
  • sign bit.
  • exponent (the number of digits to shift the mantissa by eg in 3^00 the 100 would be the mantissa)
  • mantissa (the significant digits are stored here eg in 3^100 the 3 would be the mantissa)
now a float can easily represent a number like 3^100 however it probably cannot represent 3.0000000000000000001^100 exactly because there is not sufficcient mantissa in the format of reals to store the tiny ..01 in the end.

your example also relys on R2S not having bugs ofc.
but it is a natural property of reals that there is a limit to precision.
when you have a small number this limit is a tiny little decimal but when the numbers are big enough then not every integer even can be exactly represented. this is a natural property of floats and in no way does it indicate that war3 reals are not-true-floats.

and please read the actual wiki link i posted so you know what floats are BEFORE you make a fool out of yourself.
how can you say reals are not floats if you don't understand the very basics about what a float is.
 

EdgeOfChaos

E

EdgeOfChaos

Yes? I already know these things, what is your point? You're clearly not reading my posts, or are not understand them. So I'm going to try to make it very clear.

I understand how reals work. The problem is that WC3's real is BUGGED and cannot go over 2.1 billion.

No it is not a bug with R2S. Using a real set to anything over 2.1b does not work.

When I say that it goes to the incorrect value, I don't mean losing minor precision. I mean that the real overflows and turns into a negative number. This is CLEARLY NOT expected behavior.

Because: one last time for good measure, WC3's real is BUGGED. I literally don't know how I can make this any more clear.

Maybe if you had bothered to test the code that I posted twice, you would know what I'm talking about. But you're too busy feeling superior because you understand a semester 1 basic computer science concept. Congratulations. After you're done with that, do you mind letting the people who actually READ the topic discuss this, like Dat? He actually understood it and posted two workarounds I had not considered.

I'm not trying to be mean here, but seriously - you obviously don't understand anything I've said in this entire topic, so either go and figure it out or stop posting. You're just flooding the thread with irrelevant information.
 
Last edited by a moderator:
You know what is interesting? Reals were converted into their integer representations. It was found that the integer representation was IEEE 754. This means that the representation of the real is correct, but the interpretation of it is incorrect.

JASS:
// the test was with code that was something like this
function R2IEx takes real r returns integer
    return r
    return 0
endfunction


I would also call this BigFloat or Float. How many bits are you planning to use to represent the numbers? Or are you trying to just fix floats?



As for BigInt, it does have an entirely different goal. Right in the description at the top of the API, it says unsigned integers.

I just read through the API after having not looked at it for a couple of years and I thought that it was very understandable : ). One key point in it is that the integers that it creates are mutable, as are the operations.
 

EdgeOfChaos

E

EdgeOfChaos

@Dat, thanks a lot! I might actually just use this workaround instead of implementing this class everywhere. I am interested to know what the "fatal number" (when hp rolls back to zero) is. Is it 2^64? Also, I don't really see any way to write this without vJASS since regular jass isn't object oriented, and this is more like a class than anything else.

@Nestharus, thanks for the response. It's really odd that Blizzard chose to do that, perhaps they just never expected anyone would go further than MAXINT. Currently, I'm just trying to fix floats. Obviously I can't write a native class that you can do native operations with (like adding, without a method) so this class would never be as nice as a real float. But hopefully it can work as a bandaid for cases where you need a number above MAXINT. Good suggestion to change the name. If the actual underlying real is correct, I wonder if there's any way to access it without the glitchy conversion.
 
Last edited by a moderator:
@Dat, thanks a lot! I might actually just use this workaround instead of implementing this class everywhere. I am interested to know what the "fatal number" (when hp rolls back to zero) is. Is it 2^64? Also, I don't really see any way to write this without vJASS since regular jass isn't object oriented, and this is more like a class than anything else.

Fatal number is either 0.405 or past 27 zero's, to get pass 2.1bla billion health you need to revive the unit or cancel/stop their death process, easy for heroes and still easy for units if you know about phoenix abilities.

I just dislike vJASS, no need to re-write it. =) Though thanks for the thought.
 

Dr Super Good

Spell Reviewer
Level 65
Joined
Jan 18, 2005
Messages
27,290
Modifying health is subject to floating point error. When you set a unit's life it actually applies a 32bit float delta to the units 32bit float current life. For relatively small changes in health this is fine, however if it changes several orders of magnitude then the resulting health may not be what you specified, possibly even killing the unit due to falling below the fatal threshold.

Your "BigFloat" implementation seems a bit iffy. You might as well make it a double precision float class out of two or more integers. That way it is at least standardized with well defined and known minimum and maximum values.

Another approach would be a vector float. Where you have a dynamic length mantissa and integer exponent. This would be able to accurately represent numbers up to some unreasonable precision.
 
Status
Not open for further replies.
Top