/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.util;

/**
 * Floating point numbers smaller than 32 bits.
 *
 * @lucene.internal
 */
public class SmallFloat {

  /** No instance */
  private SmallFloat() {}

  /**
   * Converts a 32 bit float to an 8 bit float. <br>
   * Values less than zero are all mapped to zero. <br>
   * Values are truncated (rounded down) to the nearest 8 bit value. <br>
   * Values between zero and the smallest representable value are rounded up.
   *
   * @param f the 32 bit float to be converted to an 8 bit float (byte)
   * @param numMantissaBits the number of mantissa bits to use in the byte, with the remainder to be
   *     used in the exponent
   * @param zeroExp the zero-point in the range of exponent values
   * @return the 8 bit float representation
   */
  public static byte floatToByte(float f, int numMantissaBits, int zeroExp) {
    // Adjustment from a float zero exponent to our zero exponent,
    // shifted over to our exponent position.
    int fzero = (63 - zeroExp) << numMantissaBits;
    int bits = Float.floatToRawIntBits(f);
    int smallfloat = bits >> (24 - numMantissaBits);
    if (smallfloat <= fzero) {
      return (bits <= 0)
          ? (byte) 0 // negative numbers and zero both map to 0 byte
          : (byte) 1; // underflow is mapped to smallest non-zero number.
    } else if (smallfloat >= fzero + 0x100) {
      return -1; // overflow maps to largest number
    } else {
      return (byte) (smallfloat - fzero);
    }
  }

  /** Converts an 8 bit float to a 32 bit float. */
  public static float byteToFloat(byte b, int numMantissaBits, int zeroExp) {
    // on Java1.5 & 1.6 JVMs, prebuilding a decoding array and doing a lookup
    // is only a little bit faster (anywhere from 0% to 7%)
    if (b == 0) return 0.0f;
    int bits = (b & 0xff) << (24 - numMantissaBits);
    bits += (63 - zeroExp) << 24;
    return Float.intBitsToFloat(bits);
  }

  //
  // Some specializations of the generic functions follow.
  // The generic functions are just as fast with current (1.5)
  // -server JVMs, but still slower with client JVMs.
  //

  /**
   * floatToByte(b, mantissaBits=3, zeroExponent=15) <br>
   * smallest non-zero value = 5.820766E-10 <br>
   * largest value = 7.5161928E9 <br>
   * epsilon = 0.125
   */
  public static byte floatToByte315(float f) {
    int bits = Float.floatToRawIntBits(f);
    int smallfloat = bits >> (24 - 3);
    if (smallfloat <= ((63 - 15) << 3)) {
      return (bits <= 0) ? (byte) 0 : (byte) 1;
    }
    if (smallfloat >= ((63 - 15) << 3) + 0x100) {
      return -1;
    }
    return (byte) (smallfloat - ((63 - 15) << 3));
  }

  /** byteToFloat(b, mantissaBits=3, zeroExponent=15) */
  public static float byte315ToFloat(byte b) {
    // on Java1.5 & 1.6 JVMs, prebuilding a decoding array and doing a lookup
    // is only a little bit faster (anywhere from 0% to 7%)
    if (b == 0) return 0.0f;
    int bits = (b & 0xff) << (24 - 3);
    bits += (63 - 15) << 24;
    return Float.intBitsToFloat(bits);
  }

  /** Float-like encoding for positive longs that preserves ordering and 4 significant bits. */
  public static int longToInt4(long i) {
    if (i < 0) {
      throw new IllegalArgumentException("Only supports positive values, got " + i);
    }
    int numBits = 64 - Long.numberOfLeadingZeros(i);
    if (numBits < 4) {
      // subnormal value
      return Math.toIntExact(i);
    } else {
      // normal value
      int shift = numBits - 4;
      // only keep the 5 most significant bits
      int encoded = Math.toIntExact(i >>> shift);
      // clear the most significant bit, which is implicit
      encoded &= 0x07;
      // encode the shift, adding 1 because 0 is reserved for subnormal values
      encoded |= (shift + 1) << 3;
      return encoded;
    }
  }

  /** Decode values encoded with {@link #longToInt4(long)}. */
  public static final long int4ToLong(int i) {
    long bits = i & 0x07;
    int shift = (i >>> 3) - 1;
    long decoded;
    if (shift == -1) {
      // subnormal value
      decoded = bits;
    } else {
      // normal value
      decoded = (bits | 0x08) << shift;
    }
    return decoded;
  }

  private static final int MAX_INT4 = longToInt4(Integer.MAX_VALUE);
  private static final int NUM_FREE_VALUES = 255 - MAX_INT4;

  /**
   * Encode an integer to a byte. It is built upon {@link #longToInt4(long)} and leverages the fact
   * that {@code longToInt4(Integer.MAX_VALUE)} is less than 255 to encode low values more
   * accurately.
   */
  public static byte intToByte4(int i) {
    if (i < 0) {
      throw new IllegalArgumentException("Only supports positive values, got " + i);
    }
    if (i < NUM_FREE_VALUES) {
      return (byte) i;
    } else {
      return (byte) (NUM_FREE_VALUES + longToInt4(i - NUM_FREE_VALUES));
    }
  }

  /** Decode values that have been encoded with {@link #intToByte4(int)}. */
  public static int byte4ToInt(byte b) {
    int i = Byte.toUnsignedInt(b);
    if (i < NUM_FREE_VALUES) {
      return i;
    } else {
      long decoded = NUM_FREE_VALUES + int4ToLong(i - NUM_FREE_VALUES);
      return Math.toIntExact(decoded);
    }
  }
}
